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 }
685 }
686
687 #[test]
688 fn test_simple_datastore() -> anyhow::Result<()> {
689 let mut sim = Simulator::new();
690
691 let alice = sim.bls(1);
692 let alice_p2 = StandardLayer::new(alice.pk);
693
694 let ctx = &mut SpendContext::new();
695
696 let (launch_singleton, datastore) = Launcher::new(alice.coin.coin_id(), 1).mint_datastore(
697 ctx,
698 DataStoreMetadata::root_hash_only(RootHash::Zero.value()),
699 alice.puzzle_hash.into(),
700 vec![],
701 )?;
702 alice_p2.spend(ctx, alice.coin, launch_singleton)?;
703
704 let spends = ctx.take();
705 for spend in spends {
706 if spend.coin.coin_id() == datastore.info.launcher_id {
707 let new_datastore = DataStore::from_spend(ctx, &spend, &[])?.unwrap();
708
709 assert_eq!(datastore, new_datastore);
710 }
711
712 ctx.insert(spend);
713 }
714
715 let datastore_inner_spend = alice_p2.spend_with_conditions(
716 ctx,
717 Conditions::new().create_coin(alice.puzzle_hash, 1, Memos::None),
718 )?;
719
720 let old_datastore_coin = datastore.coin;
721 let new_spend = datastore.spend(ctx, datastore_inner_spend)?;
722
723 ctx.insert(new_spend);
724
725 sim.spend_coins(ctx.take(), &[alice.sk])?;
726
727 let coin_state = sim
729 .coin_state(old_datastore_coin.coin_id())
730 .expect("expected datastore coin");
731 assert_eq!(coin_state.coin, old_datastore_coin);
732 assert!(coin_state.spent_height.is_some());
733
734 Ok(())
735 }
736
737 #[allow(clippy::similar_names)]
738 #[test]
739 fn test_datastore_with_delegation_layer() -> anyhow::Result<()> {
740 let mut sim = Simulator::new();
741
742 let [owner, admin, writer] = BlsPair::range();
743
744 let oracle_puzzle_hash: Bytes32 = [1; 32].into();
745 let oracle_fee = 1000;
746
747 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
748 let coin = sim.new_coin(owner_puzzle_hash, 1);
749
750 let ctx = &mut SpendContext::new();
751
752 let admin_puzzle = ctx.curry(StandardArgs::new(admin.pk))?;
753 let admin_puzzle_hash = ctx.tree_hash(admin_puzzle);
754
755 let writer_inner_puzzle = ctx.curry(StandardArgs::new(writer.pk))?;
756 let writer_inner_puzzle_hash = ctx.tree_hash(writer_inner_puzzle);
757
758 let admin_delegated_puzzle = DelegatedPuzzle::Admin(admin_puzzle_hash);
759 let writer_delegated_puzzle = DelegatedPuzzle::Writer(writer_inner_puzzle_hash);
760
761 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
762
763 let (launch_singleton, datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
764 ctx,
765 DataStoreMetadata::default(),
766 owner_puzzle_hash.into(),
767 vec![
768 admin_delegated_puzzle,
769 writer_delegated_puzzle,
770 oracle_delegated_puzzle,
771 ],
772 )?;
773 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
774
775 let spends = ctx.take();
776 for spend in spends {
777 if spend.coin.coin_id() == datastore.info.launcher_id {
778 let new_datastore = DataStore::from_spend(ctx, &spend, &[])?.unwrap();
779
780 assert_eq!(datastore, new_datastore);
781 }
782
783 ctx.insert(spend);
784 }
785
786 assert_eq!(datastore.info.metadata.root_hash, RootHash::Zero.value());
787
788 let new_metadata = metadata_from_tuple((
790 RootHash::Some,
791 Label::Some,
792 Description::Some,
793 ByteSize::Some,
794 ));
795
796 let new_metadata_condition = DataStore::new_metadata_condition(ctx, new_metadata.clone())?;
797
798 let inner_spend = WriterLayer::new(StandardLayer::new(writer.pk))
799 .spend(ctx, Conditions::new().with(new_metadata_condition))?;
800 let new_spend = datastore.clone().spend(ctx, inner_spend)?;
801
802 let datastore = DataStore::<DataStoreMetadata>::from_spend(
803 ctx,
804 &new_spend,
805 &datastore.info.delegated_puzzles,
806 )?
807 .unwrap();
808 ctx.insert(new_spend);
809
810 assert_eq!(datastore.info.metadata, new_metadata);
811
812 let delegated_puzzles = vec![admin_delegated_puzzle, oracle_delegated_puzzle];
814 let new_merkle_tree = get_merkle_tree(ctx, delegated_puzzles.clone())?;
815 let new_merkle_root = new_merkle_tree.root();
816
817 let new_merkle_root_condition = ctx.alloc(&UpdateDataStoreMerkleRoot {
818 new_merkle_root,
819 memos: DataStore::<DataStoreMetadata>::get_recreation_memos(
820 datastore.info.launcher_id,
821 owner_puzzle_hash.into(),
822 delegated_puzzles.clone(),
823 ),
824 })?;
825
826 let inner_spend = StandardLayer::new(admin.pk).spend_with_conditions(
827 ctx,
828 Conditions::new().with(Condition::Other(new_merkle_root_condition)),
829 )?;
830 let new_spend = datastore.clone().spend(ctx, inner_spend)?;
831
832 let datastore = DataStore::<DataStoreMetadata>::from_spend(
833 ctx,
834 &new_spend,
835 &datastore.info.delegated_puzzles,
836 )?
837 .unwrap();
838 ctx.insert(new_spend);
839
840 assert!(!datastore.info.delegated_puzzles.is_empty());
841 assert_eq!(datastore.info.delegated_puzzles, delegated_puzzles);
842
843 let oracle_layer = OracleLayer::new(oracle_puzzle_hash, oracle_fee).unwrap();
846 let inner_datastore_spend = oracle_layer.construct_spend(ctx, ())?;
847
848 let new_spend = datastore.clone().spend(ctx, inner_datastore_spend)?;
849
850 let new_datastore = DataStore::<DataStoreMetadata>::from_spend(
851 ctx,
852 &new_spend,
853 &datastore.info.delegated_puzzles,
854 )?
855 .unwrap();
856 ctx.insert(new_spend);
857
858 assert_eq!(new_datastore.info, new_datastore.info);
859 let datastore = new_datastore;
860
861 let new_coin = sim.new_coin(owner_puzzle_hash, oracle_fee);
863
864 let mut hasher = Sha256::new();
865 hasher.update(datastore.coin.puzzle_hash);
866 hasher.update(Bytes::new("$".into()).to_vec());
867
868 StandardLayer::new(owner.pk).spend(
869 ctx,
870 new_coin,
871 Conditions::new().assert_puzzle_announcement(Bytes32::new(hasher.finalize())),
872 )?;
873
874 let owner_layer = StandardLayer::new(owner.pk);
876 let output_condition = DataStore::<DataStoreMetadata>::owner_create_coin_condition(
877 ctx,
878 datastore.info.launcher_id,
879 owner_puzzle_hash,
880 vec![],
881 true,
882 )?;
883 let datastore_remove_delegation_layer_inner_spend =
884 owner_layer.spend_with_conditions(ctx, Conditions::new().with(output_condition))?;
885 let new_spend = datastore
886 .clone()
887 .spend(ctx, datastore_remove_delegation_layer_inner_spend)?;
888
889 let new_datastore =
890 DataStore::<DataStoreMetadata>::from_spend(ctx, &new_spend, &[])?.unwrap();
891 ctx.insert(new_spend);
892
893 assert!(new_datastore.info.delegated_puzzles.is_empty());
894 assert_eq!(new_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
895
896 sim.spend_coins(ctx.take(), &[owner.sk, admin.sk, writer.sk])?;
897
898 let coin_state = sim
900 .coin_state(new_datastore.coin.parent_coin_info)
901 .expect("expected datastore coin");
902 assert_eq!(coin_state.coin, datastore.coin);
903 assert!(coin_state.spent_height.is_some());
904
905 Ok(())
906 }
907
908 #[derive(PartialEq, Debug, Clone, Copy)]
909 pub enum DstAdminLayer {
910 None,
911 Same,
912 New,
913 }
914
915 fn assert_delegated_puzzles_contain(
916 dps: &[DelegatedPuzzle],
917 values: &[DelegatedPuzzle],
918 contained: &[bool],
919 ) {
920 for (i, value) in values.iter().enumerate() {
921 assert_eq!(dps.iter().any(|dp| dp == value), contained[i]);
922 }
923 }
924
925 #[rstest(
926 src_with_writer => [true, false],
927 src_with_oracle => [true, false],
928 dst_with_writer => [true, false],
929 dst_with_oracle => [true, false],
930 src_meta => [
931 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
932 (RootHash::Some, Label::Some, Description::Some, ByteSize::Some),
933 ],
934 dst_meta => [
935 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
936 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
937 (RootHash::Zero, Label::New, Description::New, ByteSize::New),
938 ],
939 dst_admin => [
940 DstAdminLayer::None,
941 DstAdminLayer::Same,
942 DstAdminLayer::New,
943 ]
944 )]
945 #[test]
946 fn test_datastore_admin_transition(
947 src_meta: (RootHash, Label, Description, ByteSize),
948 src_with_writer: bool,
949 src_with_oracle: bool,
951 dst_with_writer: bool,
952 dst_with_oracle: bool,
953 dst_admin: DstAdminLayer,
954 dst_meta: (RootHash, Label, Description, ByteSize),
955 ) -> anyhow::Result<()> {
956 let mut sim = Simulator::new();
957
958 let [owner, admin, admin2, writer] = BlsPair::range();
959
960 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
961 let oracle_fee = 1000;
962
963 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
964 let coin = sim.new_coin(owner_puzzle_hash, 1);
965
966 let ctx = &mut SpendContext::new();
967
968 let admin_delegated_puzzle =
969 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
970 let admin2_delegated_puzzle =
971 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin2.pk));
972 let writer_delegated_puzzle =
973 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
974 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
975
976 let mut src_delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
977 src_delegated_puzzles.push(admin_delegated_puzzle);
978 if src_with_writer {
979 src_delegated_puzzles.push(writer_delegated_puzzle);
980 }
981 if src_with_oracle {
982 src_delegated_puzzles.push(oracle_delegated_puzzle);
983 }
984
985 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
986 ctx,
987 metadata_from_tuple(src_meta),
988 owner_puzzle_hash.into(),
989 src_delegated_puzzles.clone(),
990 )?;
991
992 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
993
994 let mut admin_inner_output = Conditions::new();
996
997 let mut dst_delegated_puzzles: Vec<DelegatedPuzzle> = src_delegated_puzzles.clone();
998 if src_with_writer != dst_with_writer
999 || src_with_oracle != dst_with_oracle
1000 || dst_admin != DstAdminLayer::Same
1001 {
1002 dst_delegated_puzzles.clear();
1003
1004 if dst_with_writer {
1005 dst_delegated_puzzles.push(writer_delegated_puzzle);
1006 }
1007 if dst_with_oracle {
1008 dst_delegated_puzzles.push(oracle_delegated_puzzle);
1009 }
1010
1011 match dst_admin {
1012 DstAdminLayer::None => {}
1013 DstAdminLayer::Same => {
1014 dst_delegated_puzzles.push(admin_delegated_puzzle);
1015 }
1016 DstAdminLayer::New => {
1017 dst_delegated_puzzles.push(admin2_delegated_puzzle);
1018 }
1019 }
1020
1021 let new_merkle_tree = get_merkle_tree(ctx, dst_delegated_puzzles.clone())?;
1022
1023 let new_merkle_root_condition = ctx.alloc(&UpdateDataStoreMerkleRoot {
1024 new_merkle_root: new_merkle_tree.root(),
1025 memos: DataStore::<DataStoreMetadata>::get_recreation_memos(
1026 src_datastore.info.launcher_id,
1027 owner_puzzle_hash.into(),
1028 dst_delegated_puzzles.clone(),
1029 ),
1030 })?;
1031
1032 admin_inner_output =
1033 admin_inner_output.with(Condition::Other(new_merkle_root_condition));
1034 }
1035
1036 if src_meta != dst_meta {
1037 let new_metadata = metadata_from_tuple(dst_meta);
1038
1039 admin_inner_output =
1040 admin_inner_output.with(DataStore::new_metadata_condition(ctx, new_metadata)?);
1041 }
1042
1043 let inner_datastore_spend =
1045 StandardLayer::new(admin.pk).spend_with_conditions(ctx, admin_inner_output)?;
1046 let src_datastore_coin = src_datastore.coin;
1047 let new_spend = src_datastore.clone().spend(ctx, inner_datastore_spend)?;
1048
1049 let dst_datastore = DataStore::<DataStoreMetadata>::from_spend(
1050 ctx,
1051 &new_spend,
1052 &src_datastore.info.delegated_puzzles,
1053 )?
1054 .unwrap();
1055 ctx.insert(new_spend);
1056
1057 assert_eq!(src_datastore.info.delegated_puzzles, src_delegated_puzzles);
1058 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1059
1060 assert_eq!(src_datastore.info.metadata, metadata_from_tuple(src_meta));
1061
1062 assert_delegated_puzzles_contain(
1063 &src_datastore.info.delegated_puzzles,
1064 &[
1065 admin2_delegated_puzzle,
1066 admin_delegated_puzzle,
1067 writer_delegated_puzzle,
1068 oracle_delegated_puzzle,
1069 ],
1070 &[false, true, src_with_writer, src_with_oracle],
1071 );
1072
1073 assert_eq!(dst_datastore.info.delegated_puzzles, dst_delegated_puzzles);
1074 assert_eq!(dst_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1075
1076 assert_eq!(dst_datastore.info.metadata, metadata_from_tuple(dst_meta));
1077
1078 assert_delegated_puzzles_contain(
1079 &dst_datastore.info.delegated_puzzles,
1080 &[
1081 admin2_delegated_puzzle,
1082 admin_delegated_puzzle,
1083 writer_delegated_puzzle,
1084 oracle_delegated_puzzle,
1085 ],
1086 &[
1087 dst_admin == DstAdminLayer::New,
1088 dst_admin == DstAdminLayer::Same,
1089 dst_with_writer,
1090 dst_with_oracle,
1091 ],
1092 );
1093
1094 sim.spend_coins(ctx.take(), &[owner.sk, admin.sk, writer.sk])?;
1095
1096 let src_coin_state = sim
1097 .coin_state(src_datastore_coin.coin_id())
1098 .expect("expected src datastore coin");
1099 assert_eq!(src_coin_state.coin, src_datastore_coin);
1100 assert!(src_coin_state.spent_height.is_some());
1101 let dst_coin_state = sim
1102 .coin_state(dst_datastore.coin.coin_id())
1103 .expect("expected dst datastore coin");
1104 assert_eq!(dst_coin_state.coin, dst_datastore.coin);
1105 assert!(dst_coin_state.created_height.is_some());
1106
1107 Ok(())
1108 }
1109
1110 #[rstest(
1111 src_with_admin => [true, false],
1112 src_with_writer => [true, false],
1113 src_with_oracle => [true, false],
1114 dst_with_admin => [true, false],
1115 dst_with_writer => [true, false],
1116 dst_with_oracle => [true, false],
1117 src_meta => [
1118 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1119 (RootHash::Some, Label::Some, Description::Some, ByteSize::Some),
1120 ],
1121 dst_meta => [
1122 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1123 (RootHash::Some, Label::Some, Description::Some, ByteSize::Some),
1124 (RootHash::Some, Label::New, Description::New, ByteSize::New),
1125 ],
1126 change_owner => [true, false],
1127 )]
1128 #[test]
1129 fn test_datastore_owner_transition(
1130 src_meta: (RootHash, Label, Description, ByteSize),
1131 src_with_admin: bool,
1132 src_with_writer: bool,
1133 src_with_oracle: bool,
1134 dst_with_admin: bool,
1135 dst_with_writer: bool,
1136 dst_with_oracle: bool,
1137 dst_meta: (RootHash, Label, Description, ByteSize),
1138 change_owner: bool,
1139 ) -> anyhow::Result<()> {
1140 let mut sim = Simulator::new();
1141
1142 let [owner, owner2, admin, writer] = BlsPair::range();
1143
1144 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
1145 let oracle_fee = 1000;
1146
1147 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1148 let coin = sim.new_coin(owner_puzzle_hash, 1);
1149
1150 let owner2_puzzle_hash = StandardArgs::curry_tree_hash(owner2.pk).into();
1151 assert_ne!(owner_puzzle_hash, owner2_puzzle_hash);
1152
1153 let ctx = &mut SpendContext::new();
1154
1155 let admin_delegated_puzzle =
1156 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
1157 let writer_delegated_puzzle =
1158 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
1159 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
1160
1161 let mut src_delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
1162 if src_with_admin {
1163 src_delegated_puzzles.push(admin_delegated_puzzle);
1164 }
1165 if src_with_writer {
1166 src_delegated_puzzles.push(writer_delegated_puzzle);
1167 }
1168 if src_with_oracle {
1169 src_delegated_puzzles.push(oracle_delegated_puzzle);
1170 }
1171
1172 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1173 ctx,
1174 metadata_from_tuple(src_meta),
1175 owner_puzzle_hash.into(),
1176 src_delegated_puzzles.clone(),
1177 )?;
1178 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1179
1180 let mut owner_output_conds = Conditions::new();
1182
1183 let mut dst_delegated_puzzles: Vec<DelegatedPuzzle> = src_delegated_puzzles.clone();
1184 let mut hint_new_delegated_puzzles = change_owner;
1185 if src_with_admin != dst_with_admin
1186 || src_with_writer != dst_with_writer
1187 || src_with_oracle != dst_with_oracle
1188 || dst_delegated_puzzles.is_empty()
1189 {
1190 dst_delegated_puzzles.clear();
1191 hint_new_delegated_puzzles = true;
1192
1193 if dst_with_admin {
1194 dst_delegated_puzzles.push(admin_delegated_puzzle);
1195 }
1196 if dst_with_writer {
1197 dst_delegated_puzzles.push(writer_delegated_puzzle);
1198 }
1199 if dst_with_oracle {
1200 dst_delegated_puzzles.push(oracle_delegated_puzzle);
1201 }
1202 }
1203
1204 owner_output_conds =
1205 owner_output_conds.with(DataStore::<DataStoreMetadata>::owner_create_coin_condition(
1206 ctx,
1207 src_datastore.info.launcher_id,
1208 if change_owner {
1209 owner2_puzzle_hash
1210 } else {
1211 owner_puzzle_hash
1212 },
1213 dst_delegated_puzzles.clone(),
1214 hint_new_delegated_puzzles,
1215 )?);
1216
1217 if src_meta != dst_meta {
1218 let new_metadata = metadata_from_tuple(dst_meta);
1219
1220 owner_output_conds =
1221 owner_output_conds.with(DataStore::new_metadata_condition(ctx, new_metadata)?);
1222 }
1223
1224 let inner_datastore_spend =
1226 StandardLayer::new(owner.pk).spend_with_conditions(ctx, owner_output_conds)?;
1227 let new_spend = src_datastore.clone().spend(ctx, inner_datastore_spend)?;
1228
1229 let dst_datastore = DataStore::<DataStoreMetadata>::from_spend(
1230 ctx,
1231 &new_spend,
1232 &src_datastore.info.delegated_puzzles,
1233 )?
1234 .unwrap();
1235
1236 ctx.insert(new_spend);
1237
1238 assert_eq!(src_datastore.info.delegated_puzzles, src_delegated_puzzles);
1239 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1240
1241 assert_eq!(src_datastore.info.metadata, metadata_from_tuple(src_meta));
1242
1243 assert_delegated_puzzles_contain(
1244 &src_datastore.info.delegated_puzzles,
1245 &[
1246 admin_delegated_puzzle,
1247 writer_delegated_puzzle,
1248 oracle_delegated_puzzle,
1249 ],
1250 &[src_with_admin, src_with_writer, src_with_oracle],
1251 );
1252
1253 assert_eq!(dst_datastore.info.delegated_puzzles, dst_delegated_puzzles);
1254 assert_eq!(
1255 dst_datastore.info.owner_puzzle_hash,
1256 if change_owner {
1257 owner2_puzzle_hash
1258 } else {
1259 owner_puzzle_hash
1260 }
1261 );
1262
1263 assert_eq!(dst_datastore.info.metadata, metadata_from_tuple(dst_meta));
1264
1265 assert_delegated_puzzles_contain(
1266 &dst_datastore.info.delegated_puzzles,
1267 &[
1268 admin_delegated_puzzle,
1269 writer_delegated_puzzle,
1270 oracle_delegated_puzzle,
1271 ],
1272 &[dst_with_admin, dst_with_writer, dst_with_oracle],
1273 );
1274
1275 sim.spend_coins(ctx.take(), &[owner.sk, admin.sk, writer.sk])?;
1276
1277 let src_coin_state = sim
1278 .coin_state(src_datastore.coin.coin_id())
1279 .expect("expected src datastore coin");
1280 assert_eq!(src_coin_state.coin, src_datastore.coin);
1281 assert!(src_coin_state.spent_height.is_some());
1282
1283 let dst_coin_state = sim
1284 .coin_state(dst_datastore.coin.coin_id())
1285 .expect("expected dst datastore coin");
1286 assert_eq!(dst_coin_state.coin, dst_datastore.coin);
1287 assert!(dst_coin_state.created_height.is_some());
1288
1289 Ok(())
1290 }
1291
1292 #[rstest(
1293 with_admin_layer => [true, false],
1294 with_oracle_layer => [true, false],
1295 meta_transition => [
1296 (
1297 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1298 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1299 ),
1300 (
1301 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1302 (RootHash::Some, Label::None, Description::None, ByteSize::None),
1303 ),
1304 (
1305 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1306 (RootHash::Some, Label::Some, Description::Some, ByteSize::Some),
1307 ),
1308 (
1309 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1310 (RootHash::Zero, Label::New, Description::New, ByteSize::New),
1311 ),
1312 (
1313 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1314 (RootHash::Zero, Label::None, Description::None, ByteSize::Some),
1315 ),
1316 (
1317 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1318 (RootHash::Zero, Label::None, Description::Some, ByteSize::Some),
1319 ),
1320 ],
1321 )]
1322 #[test]
1323 fn test_datastore_writer_transition(
1324 with_admin_layer: bool,
1325 with_oracle_layer: bool,
1326 meta_transition: (
1327 (RootHash, Label, Description, ByteSize),
1328 (RootHash, Label, Description, ByteSize),
1329 ),
1330 ) -> anyhow::Result<()> {
1331 let mut sim = Simulator::new();
1332
1333 let [owner, admin, writer] = BlsPair::range();
1334
1335 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
1336 let oracle_fee = 1000;
1337
1338 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1339 let coin = sim.new_coin(owner_puzzle_hash, 1);
1340
1341 let ctx = &mut SpendContext::new();
1342
1343 let admin_delegated_puzzle =
1344 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
1345 let writer_delegated_puzzle =
1346 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
1347 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
1348
1349 let mut delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
1350 delegated_puzzles.push(writer_delegated_puzzle);
1351 if with_admin_layer {
1352 delegated_puzzles.push(admin_delegated_puzzle);
1353 }
1354 if with_oracle_layer {
1355 delegated_puzzles.push(oracle_delegated_puzzle);
1356 }
1357
1358 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1359 ctx,
1360 metadata_from_tuple(meta_transition.0),
1361 owner_puzzle_hash.into(),
1362 delegated_puzzles.clone(),
1363 )?;
1364
1365 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1366
1367 let new_metadata = metadata_from_tuple(meta_transition.1);
1369 let new_metadata_condition = DataStore::new_metadata_condition(ctx, new_metadata)?;
1370
1371 let inner_spend = WriterLayer::new(StandardLayer::new(writer.pk))
1372 .spend(ctx, Conditions::new().with(new_metadata_condition))?;
1373
1374 let new_spend = src_datastore.clone().spend(ctx, inner_spend)?;
1375
1376 let dst_datastore = DataStore::<DataStoreMetadata>::from_spend(
1377 ctx,
1378 &new_spend,
1379 &src_datastore.info.delegated_puzzles,
1380 )?
1381 .unwrap();
1382 ctx.insert(new_spend.clone());
1383
1384 assert_eq!(src_datastore.info.delegated_puzzles, delegated_puzzles);
1385 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1386
1387 assert_eq!(
1388 src_datastore.info.metadata,
1389 metadata_from_tuple(meta_transition.0)
1390 );
1391
1392 assert_delegated_puzzles_contain(
1393 &src_datastore.info.delegated_puzzles,
1394 &[
1395 admin_delegated_puzzle,
1396 writer_delegated_puzzle,
1397 oracle_delegated_puzzle,
1398 ],
1399 &[with_admin_layer, true, with_oracle_layer],
1400 );
1401
1402 assert_eq!(dst_datastore.info.delegated_puzzles, delegated_puzzles);
1403 assert_eq!(dst_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1404
1405 assert_eq!(
1406 dst_datastore.info.metadata,
1407 metadata_from_tuple(meta_transition.1)
1408 );
1409
1410 assert_delegated_puzzles_contain(
1411 &dst_datastore.info.delegated_puzzles,
1412 &[
1413 admin_delegated_puzzle,
1414 writer_delegated_puzzle,
1415 oracle_delegated_puzzle,
1416 ],
1417 &[with_admin_layer, true, with_oracle_layer],
1418 );
1419
1420 sim.spend_coins(ctx.take(), &[owner.sk, admin.sk, writer.sk])?;
1421
1422 let src_coin_state = sim
1423 .coin_state(src_datastore.coin.coin_id())
1424 .expect("expected src datastore coin");
1425 assert_eq!(src_coin_state.coin, src_datastore.coin);
1426 assert!(src_coin_state.spent_height.is_some());
1427 let dst_coin_state = sim
1428 .coin_state(dst_datastore.coin.coin_id())
1429 .expect("expected dst datastore coin");
1430 assert_eq!(dst_coin_state.coin, dst_datastore.coin);
1431 assert!(dst_coin_state.created_height.is_some());
1432
1433 Ok(())
1434 }
1435
1436 #[rstest(
1437 with_admin_layer => [true, false],
1438 with_writer_layer => [true, false],
1439 meta => [
1440 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1441 (RootHash::Zero, Label::None, Description::None, ByteSize::Some),
1442 (RootHash::Zero, Label::None, Description::Some, ByteSize::Some),
1443 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1444 ],
1445 )]
1446 #[test]
1447 fn test_datastore_oracle_transition(
1448 with_admin_layer: bool,
1449 with_writer_layer: bool,
1450 meta: (RootHash, Label, Description, ByteSize),
1451 ) -> anyhow::Result<()> {
1452 let mut sim = Simulator::new();
1453
1454 let [owner, admin, writer, dude] = BlsPair::range();
1455
1456 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
1457 let oracle_fee = 1000;
1458
1459 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1460 let coin = sim.new_coin(owner_puzzle_hash, 1);
1461
1462 let dude_puzzle_hash = StandardArgs::curry_tree_hash(dude.pk).into();
1463
1464 let ctx = &mut SpendContext::new();
1465
1466 let admin_delegated_puzzle =
1467 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
1468 let writer_delegated_puzzle =
1469 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
1470 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
1471
1472 let mut delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
1473 delegated_puzzles.push(oracle_delegated_puzzle);
1474
1475 if with_admin_layer {
1476 delegated_puzzles.push(admin_delegated_puzzle);
1477 }
1478 if with_writer_layer {
1479 delegated_puzzles.push(writer_delegated_puzzle);
1480 }
1481
1482 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1483 ctx,
1484 metadata_from_tuple(meta),
1485 owner_puzzle_hash.into(),
1486 delegated_puzzles.clone(),
1487 )?;
1488
1489 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1490
1491 let inner_datastore_spend = OracleLayer::new(oracle_puzzle_hash, oracle_fee)
1493 .unwrap()
1494 .spend(ctx)?;
1495 let new_spend = src_datastore.clone().spend(ctx, inner_datastore_spend)?;
1496
1497 let dst_datastore =
1498 DataStore::from_spend(ctx, &new_spend, &src_datastore.info.delegated_puzzles)?.unwrap();
1499 ctx.insert(new_spend);
1500
1501 assert_eq!(src_datastore.info, dst_datastore.info);
1502
1503 let mut hasher = Sha256::new();
1505 hasher.update(src_datastore.coin.puzzle_hash);
1506 hasher.update(Bytes::new("$".into()).to_vec());
1507
1508 let new_coin = sim.new_coin(dude_puzzle_hash, oracle_fee);
1509 StandardLayer::new(dude.pk).spend(
1510 ctx,
1511 new_coin,
1512 Conditions::new().assert_puzzle_announcement(Bytes32::new(hasher.finalize())),
1513 )?;
1514
1515 assert_eq!(src_datastore.info.delegated_puzzles, delegated_puzzles);
1518 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1519
1520 assert_eq!(src_datastore.info.metadata, metadata_from_tuple(meta));
1521
1522 assert_delegated_puzzles_contain(
1523 &src_datastore.info.delegated_puzzles,
1524 &[
1525 admin_delegated_puzzle,
1526 writer_delegated_puzzle,
1527 oracle_delegated_puzzle,
1528 ],
1529 &[with_admin_layer, with_writer_layer, true],
1530 );
1531
1532 assert_eq!(dst_datastore.info.delegated_puzzles, delegated_puzzles);
1533 assert_eq!(dst_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1534
1535 assert_eq!(dst_datastore.info.metadata, metadata_from_tuple(meta));
1536
1537 assert_delegated_puzzles_contain(
1538 &dst_datastore.info.delegated_puzzles,
1539 &[
1540 admin_delegated_puzzle,
1541 writer_delegated_puzzle,
1542 oracle_delegated_puzzle,
1543 ],
1544 &[with_admin_layer, with_writer_layer, true],
1545 );
1546
1547 sim.spend_coins(ctx.take(), &[owner.sk, dude.sk])?;
1548
1549 let src_datastore_coin_id = src_datastore.coin.coin_id();
1550 let src_coin_state = sim
1551 .coin_state(src_datastore_coin_id)
1552 .expect("expected src datastore coin");
1553 assert_eq!(src_coin_state.coin, src_datastore.coin);
1554 assert!(src_coin_state.spent_height.is_some());
1555 let dst_coin_state = sim
1556 .coin_state(dst_datastore.coin.coin_id())
1557 .expect("expected dst datastore coin");
1558 assert_eq!(dst_coin_state.coin, dst_datastore.coin);
1559 assert!(dst_coin_state.created_height.is_some());
1560
1561 let oracle_coin = Coin::new(src_datastore_coin_id, oracle_puzzle_hash, oracle_fee);
1562 let oracle_coin_state = sim
1563 .coin_state(oracle_coin.coin_id())
1564 .expect("expected oracle coin");
1565 assert_eq!(oracle_coin_state.coin, oracle_coin);
1566 assert!(oracle_coin_state.created_height.is_some());
1567
1568 Ok(())
1569 }
1570
1571 #[rstest(
1572 with_admin_layer => [true, false],
1573 with_writer_layer => [true, false],
1574 with_oracle_layer => [true, false],
1575 meta => [
1576 (RootHash::Zero, Label::None, Description::None, ByteSize::None),
1577 (RootHash::Zero, Label::Some, Description::Some, ByteSize::Some),
1578 ],
1579 )]
1580 #[test]
1581 fn test_melt(
1582 with_admin_layer: bool,
1583 with_writer_layer: bool,
1584 with_oracle_layer: bool,
1585 meta: (RootHash, Label, Description, ByteSize),
1586 ) -> anyhow::Result<()> {
1587 let mut sim = Simulator::new();
1588
1589 let [owner, admin, writer] = BlsPair::range();
1590
1591 let oracle_puzzle_hash: Bytes32 = [7; 32].into();
1592 let oracle_fee = 1000;
1593
1594 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1595 let coin = sim.new_coin(owner_puzzle_hash, 1);
1596
1597 let ctx = &mut SpendContext::new();
1598
1599 let admin_delegated_puzzle =
1600 DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(admin.pk));
1601 let writer_delegated_puzzle =
1602 DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(writer.pk));
1603 let oracle_delegated_puzzle = DelegatedPuzzle::Oracle(oracle_puzzle_hash, oracle_fee);
1604
1605 let mut delegated_puzzles: Vec<DelegatedPuzzle> = vec![];
1606 if with_admin_layer {
1607 delegated_puzzles.push(admin_delegated_puzzle);
1608 }
1609 if with_writer_layer {
1610 delegated_puzzles.push(writer_delegated_puzzle);
1611 }
1612 if with_oracle_layer {
1613 delegated_puzzles.push(oracle_delegated_puzzle);
1614 }
1615
1616 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1617 ctx,
1618 metadata_from_tuple(meta),
1619 owner_puzzle_hash.into(),
1620 delegated_puzzles.clone(),
1621 )?;
1622
1623 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1624
1625 let output_conds = Conditions::new().melt_singleton();
1627 let inner_datastore_spend =
1628 StandardLayer::new(owner.pk).spend_with_conditions(ctx, output_conds)?;
1629
1630 let new_spend = src_datastore.clone().spend(ctx, inner_datastore_spend)?;
1631 ctx.insert(new_spend);
1632
1633 assert_eq!(src_datastore.info.owner_puzzle_hash, owner_puzzle_hash);
1636
1637 assert_eq!(src_datastore.info.metadata, metadata_from_tuple(meta));
1638
1639 assert_delegated_puzzles_contain(
1640 &src_datastore.info.delegated_puzzles,
1641 &[
1642 admin_delegated_puzzle,
1643 writer_delegated_puzzle,
1644 oracle_delegated_puzzle,
1645 ],
1646 &[with_admin_layer, with_writer_layer, with_oracle_layer],
1647 );
1648
1649 sim.spend_coins(ctx.take(), &[owner.sk])?;
1650
1651 let src_coin_state = sim
1652 .coin_state(src_datastore.coin.coin_id())
1653 .expect("expected src datastore coin");
1654 assert_eq!(src_coin_state.coin, src_datastore.coin);
1655 assert!(src_coin_state.spent_height.is_some()); Ok(())
1658 }
1659
1660 enum AttackerPuzzle {
1661 Admin,
1662 Writer,
1663 }
1664
1665 impl AttackerPuzzle {
1666 fn get_spend(
1667 &self,
1668 ctx: &mut SpendContext,
1669 attacker_pk: PublicKey,
1670 output_conds: Conditions,
1671 ) -> Result<Spend, DriverError> {
1672 Ok(match self {
1673 AttackerPuzzle::Admin => {
1674 StandardLayer::new(attacker_pk).spend_with_conditions(ctx, output_conds)?
1675 }
1676
1677 AttackerPuzzle::Writer => {
1678 WriterLayer::new(StandardLayer::new(attacker_pk)).spend(ctx, output_conds)?
1679 }
1680 })
1681 }
1682 }
1683
1684 #[rstest(
1685 puzzle => [AttackerPuzzle::Admin, AttackerPuzzle::Writer],
1686 )]
1687 #[test]
1688 fn test_create_coin_filer(puzzle: AttackerPuzzle) -> anyhow::Result<()> {
1689 let mut sim = Simulator::new();
1690
1691 let [owner, attacker] = BlsPair::range();
1692
1693 let owner_pk = owner.pk;
1694 let attacker_pk = attacker.pk;
1695
1696 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1697 let attacker_puzzle_hash = StandardArgs::curry_tree_hash(attacker.pk);
1698 let coin = sim.new_coin(owner_puzzle_hash, 1);
1699
1700 let ctx = &mut SpendContext::new();
1701
1702 let delegated_puzzle = match puzzle {
1703 AttackerPuzzle::Admin => DelegatedPuzzle::Admin(attacker_puzzle_hash),
1704 AttackerPuzzle::Writer => DelegatedPuzzle::Writer(attacker_puzzle_hash),
1705 };
1706
1707 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1708 ctx,
1709 DataStoreMetadata::default(),
1710 owner_puzzle_hash.into(),
1711 vec![delegated_puzzle],
1712 )?;
1713
1714 StandardLayer::new(owner_pk).spend(ctx, coin, launch_singleton)?;
1715
1716 let inner_datastore_spend = puzzle.get_spend(
1718 ctx,
1719 attacker_pk,
1720 Conditions::new().with(Condition::CreateCoin(CreateCoin {
1721 puzzle_hash: attacker_puzzle_hash.into(),
1722 amount: 1,
1723 memos: Memos::None,
1724 })),
1725 )?;
1726
1727 let new_spend = src_datastore.spend(ctx, inner_datastore_spend)?;
1728
1729 let puzzle_reveal_ptr = ctx.alloc(&new_spend.puzzle_reveal)?;
1730 let solution_ptr = ctx.alloc(&new_spend.solution)?;
1731 match ctx.run(puzzle_reveal_ptr, solution_ptr) {
1732 Ok(_) => panic!("expected error"),
1733 Err(err) => match err {
1734 DriverError::Eval(eval_err) => {
1735 assert_eq!(eval_err.1, "clvm raise");
1736 }
1737 _ => panic!("expected 'clvm raise' error"),
1738 },
1739 }
1740
1741 Ok(())
1742 }
1743
1744 #[rstest(
1745 puzzle => [AttackerPuzzle::Admin, AttackerPuzzle::Writer],
1746 )]
1747 #[test]
1748 fn test_melt_filter(puzzle: AttackerPuzzle) -> anyhow::Result<()> {
1749 let mut sim = Simulator::new();
1750
1751 let [owner, attacker] = BlsPair::range();
1752
1753 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk).into();
1754 let coin = sim.new_coin(owner_puzzle_hash, 1);
1755
1756 let attacker_puzzle_hash = StandardArgs::curry_tree_hash(attacker.pk);
1757
1758 let ctx = &mut SpendContext::new();
1759
1760 let delegated_puzzle = match puzzle {
1761 AttackerPuzzle::Admin => DelegatedPuzzle::Admin(attacker_puzzle_hash),
1762 AttackerPuzzle::Writer => DelegatedPuzzle::Writer(attacker_puzzle_hash),
1763 };
1764
1765 let (launch_singleton, src_datastore) = Launcher::new(coin.coin_id(), 1).mint_datastore(
1766 ctx,
1767 DataStoreMetadata::default(),
1768 owner_puzzle_hash.into(),
1769 vec![delegated_puzzle],
1770 )?;
1771
1772 StandardLayer::new(owner.pk).spend(ctx, coin, launch_singleton)?;
1773
1774 let conds = Conditions::new().melt_singleton();
1776 let inner_datastore_spend = puzzle.get_spend(ctx, attacker.pk, conds)?;
1777
1778 let new_spend = src_datastore.spend(ctx, inner_datastore_spend)?;
1779
1780 let puzzle_reveal_ptr = ctx.alloc(&new_spend.puzzle_reveal)?;
1781 let solution_ptr = ctx.alloc(&new_spend.solution)?;
1782 match ctx.run(puzzle_reveal_ptr, solution_ptr) {
1783 Ok(_) => panic!("expected error"),
1784 Err(err) => match err {
1785 DriverError::Eval(eval_err) => {
1786 assert_eq!(eval_err.1, "clvm raise");
1787 Ok(())
1788 }
1789 _ => panic!("expected 'clvm raise' error"),
1790 },
1791 }
1792 }
1793
1794 #[rstest(
1795 test_puzzle => [AttackerPuzzle::Admin, AttackerPuzzle::Writer],
1796 new_merkle_root => [RootHash::Zero, RootHash::Some],
1797 memos => [vec![], vec![RootHash::Zero], vec![RootHash::Some]],
1798 )]
1799 fn test_new_merkle_root_filter(
1800 test_puzzle: AttackerPuzzle,
1801 new_merkle_root: RootHash,
1802 memos: Vec<RootHash>,
1803 ) -> anyhow::Result<()> {
1804 let attacker = BlsPair::default();
1805
1806 let ctx = &mut SpendContext::new();
1807
1808 let condition_output = Conditions::new().update_data_store_merkle_root(
1809 new_merkle_root.value(),
1810 memos.into_iter().map(|m| m.value().into()).collect(),
1811 );
1812
1813 let spend = test_puzzle.get_spend(ctx, attacker.pk, condition_output)?;
1814
1815 match ctx.run(spend.puzzle, spend.solution) {
1816 Ok(_) => match test_puzzle {
1817 AttackerPuzzle::Admin => Ok(()),
1818 AttackerPuzzle::Writer => panic!("expected error from writer puzzle"),
1819 },
1820 Err(err) => match err {
1821 DriverError::Eval(eval_err) => match test_puzzle {
1822 AttackerPuzzle::Admin => panic!("expected admin puzzle to run normally"),
1823 AttackerPuzzle::Writer => {
1824 assert_eq!(eval_err.1, "clvm raise");
1825 Ok(())
1826 }
1827 },
1828 _ => panic!("other error encountered"),
1829 },
1830 }
1831 }
1832
1833 #[rstest(
1834 puzzle => [AttackerPuzzle::Admin, AttackerPuzzle::Writer],
1835 new_root_hash => [RootHash::Zero, RootHash::Some],
1836 new_updater_ph => [RootHash::Zero.value().into(), DL_METADATA_UPDATER_PUZZLE_HASH],
1837 output_conditions => [false, true],
1838 )]
1839 fn test_metadata_filter(
1840 puzzle: AttackerPuzzle,
1841 new_root_hash: RootHash,
1842 new_updater_ph: TreeHash,
1843 output_conditions: bool,
1844 ) -> anyhow::Result<()> {
1845 let should_error_out =
1846 output_conditions || new_updater_ph != DL_METADATA_UPDATER_PUZZLE_HASH;
1847
1848 let attacker = BlsPair::default();
1849
1850 let ctx = &mut SpendContext::new();
1851
1852 let new_metadata_condition = Condition::update_nft_metadata(
1853 ctx.alloc(&11)?,
1854 ctx.alloc(&NewMetadataOutput {
1855 metadata_info: NewMetadataInfo {
1856 new_metadata: DataStoreMetadata::root_hash_only(new_root_hash.value()),
1857 new_updater_puzzle_hash: new_updater_ph.into(),
1858 },
1859 conditions: if output_conditions {
1860 vec![CreateCoin::<NodePtr> {
1861 puzzle_hash: [0; 32].into(),
1862 amount: 1,
1863 memos: Memos::None,
1864 }]
1865 } else {
1866 vec![]
1867 },
1868 })?,
1869 );
1870
1871 let inner_spend = puzzle.get_spend(
1872 ctx,
1873 attacker.pk,
1874 Conditions::new().with(new_metadata_condition),
1875 )?;
1876
1877 let delegated_puzzles = match puzzle {
1878 AttackerPuzzle::Admin => {
1879 vec![DelegatedPuzzle::Admin(StandardArgs::curry_tree_hash(
1880 attacker.pk,
1881 ))]
1882 }
1883 AttackerPuzzle::Writer => vec![DelegatedPuzzle::Writer(StandardArgs::curry_tree_hash(
1884 attacker.pk,
1885 ))],
1886 };
1887 let merkle_tree = get_merkle_tree(ctx, delegated_puzzles.clone())?;
1888
1889 let delegation_layer =
1890 DelegationLayer::new(Bytes32::default(), Bytes32::default(), merkle_tree.root());
1891
1892 let puzzle_ptr = delegation_layer.construct_puzzle(ctx)?;
1893
1894 let delegated_puzzle_hash = ctx.tree_hash(inner_spend.puzzle);
1895 let solution_ptr = delegation_layer.construct_solution(
1896 ctx,
1897 DelegationLayerSolution {
1898 merkle_proof: merkle_tree.proof(delegated_puzzle_hash.into()),
1899 puzzle_reveal: inner_spend.puzzle,
1900 puzzle_solution: inner_spend.solution,
1901 },
1902 )?;
1903
1904 match ctx.run(puzzle_ptr, solution_ptr) {
1905 Ok(_) => {
1906 if should_error_out {
1907 panic!("expected puzzle to error out");
1908 } else {
1909 Ok(())
1910 }
1911 }
1912 Err(err) => match err {
1913 DriverError::Eval(eval_err) => {
1914 if should_error_out {
1915 if output_conditions {
1916 assert_eq!(eval_err.1, "= on list");
1917 } else {
1918 assert_eq!(eval_err.1, "clvm raise");
1919 }
1920 Ok(())
1921 } else {
1922 panic!("expected puzzle to not error out");
1923 }
1924 }
1925 _ => panic!("unexpected error while evaluating puzzle"),
1926 },
1927 }
1928 }
1929
1930 #[rstest(
1931 transition => [
1932 (RootHash::Zero, RootHash::Zero, true),
1933 (RootHash::Zero, RootHash::Some, false),
1934 (RootHash::Zero, RootHash::Some, true),
1935 (RootHash::Some, RootHash::Some, true),
1936 (RootHash::Some, RootHash::Some, false),
1937 (RootHash::Some, RootHash::Some, true),
1938 ]
1939 )]
1940 #[test]
1941 fn test_old_memo_format(transition: (RootHash, RootHash, bool)) -> anyhow::Result<()> {
1942 let mut sim = Simulator::new();
1943
1944 let [owner, owner2] = BlsPair::range();
1945
1946 let owner_puzzle_hash = StandardArgs::curry_tree_hash(owner.pk);
1947 let coin = sim.new_coin(owner_puzzle_hash.into(), 1);
1948
1949 let owner2_puzzle_hash = StandardArgs::curry_tree_hash(owner2.pk);
1950
1951 let ctx = &mut SpendContext::new();
1952
1953 let launcher = Launcher::new(coin.coin_id(), 1);
1955 let inner_puzzle_hash: TreeHash = owner_puzzle_hash;
1956
1957 let first_root_hash: RootHash = transition.0;
1958 let metadata_ptr = ctx.alloc(&vec![first_root_hash.value()])?;
1959 let metadata_hash = ctx.tree_hash(metadata_ptr);
1960 let state_layer_hash = CurriedProgram {
1961 program: TreeHash::new(NFT_STATE_LAYER_HASH),
1962 args: NftStateLayerArgs::<TreeHash, TreeHash> {
1963 mod_hash: NFT_STATE_LAYER_HASH.into(),
1964 metadata: metadata_hash,
1965 metadata_updater_puzzle_hash: DL_METADATA_UPDATER_PUZZLE_HASH.into(),
1966 inner_puzzle: inner_puzzle_hash,
1967 },
1968 }
1969 .tree_hash();
1970
1971 let kv_list = vec![first_root_hash.value(), owner_puzzle_hash.into()];
1974
1975 let launcher_coin = launcher.coin();
1976 let (launcher_conds, eve_coin) = launcher.spend(ctx, state_layer_hash.into(), kv_list)?;
1977
1978 StandardLayer::new(owner.pk).spend(ctx, coin, launcher_conds)?;
1979
1980 let spends = ctx.take();
1981 spends
1982 .clone()
1983 .into_iter()
1984 .for_each(|spend| ctx.insert(spend));
1985
1986 let datastore_from_launcher = spends
1987 .into_iter()
1988 .find(|spend| spend.coin.coin_id() == eve_coin.parent_coin_info)
1989 .map(|spend| DataStore::from_spend(ctx, &spend, &[]).unwrap().unwrap())
1990 .expect("expected launcher spend");
1991
1992 assert_eq!(
1993 datastore_from_launcher.info.metadata,
1994 DataStoreMetadata::root_hash_only(first_root_hash.value())
1995 );
1996 assert_eq!(
1997 datastore_from_launcher.info.owner_puzzle_hash,
1998 owner_puzzle_hash.into()
1999 );
2000 assert!(datastore_from_launcher.info.delegated_puzzles.is_empty());
2001
2002 assert_eq!(
2003 datastore_from_launcher.info.launcher_id,
2004 eve_coin.parent_coin_info
2005 );
2006 assert_eq!(datastore_from_launcher.coin.coin_id(), eve_coin.coin_id());
2007
2008 match datastore_from_launcher.proof {
2009 Proof::Eve(proof) => {
2010 assert_eq!(
2011 proof.parent_parent_coin_info,
2012 launcher_coin.parent_coin_info
2013 );
2014 assert_eq!(proof.parent_amount, launcher_coin.amount);
2015 }
2016 Proof::Lineage(_) => panic!("expected eve (not lineage) proof for info_from_launcher"),
2017 }
2018
2019 let mut inner_spend_conditions = Conditions::new();
2022
2023 let second_root_hash: RootHash = transition.1;
2024
2025 let new_metadata = DataStoreMetadata::root_hash_only(second_root_hash.value());
2026 if second_root_hash != first_root_hash {
2027 inner_spend_conditions = inner_spend_conditions.with(
2028 DataStore::new_metadata_condition(ctx, new_metadata.clone())?,
2029 );
2030 }
2031
2032 let new_owner: bool = transition.2;
2033 let new_inner_ph: Bytes32 = if new_owner {
2034 owner2_puzzle_hash.into()
2035 } else {
2036 owner_puzzle_hash.into()
2037 };
2038
2039 inner_spend_conditions = inner_spend_conditions.with(Condition::CreateCoin(CreateCoin {
2042 puzzle_hash: new_inner_ph,
2043 amount: 1,
2044 memos: ctx.memos(&[
2045 launcher_coin.coin_id(),
2046 second_root_hash.value(),
2047 new_inner_ph,
2048 ])?,
2049 }));
2050
2051 let inner_spend =
2052 StandardLayer::new(owner.pk).spend_with_conditions(ctx, inner_spend_conditions)?;
2053 let spend = datastore_from_launcher.clone().spend(ctx, inner_spend)?;
2054
2055 let new_datastore = DataStore::<DataStoreMetadata>::from_spend(
2056 ctx,
2057 &spend,
2058 &datastore_from_launcher.info.delegated_puzzles,
2059 )?
2060 .unwrap();
2061
2062 assert_eq!(
2063 new_datastore.info.metadata,
2064 DataStoreMetadata::root_hash_only(second_root_hash.value())
2065 );
2066
2067 assert!(new_datastore.info.delegated_puzzles.is_empty());
2068
2069 assert_eq!(new_datastore.info.owner_puzzle_hash, new_inner_ph);
2070 assert_eq!(new_datastore.info.launcher_id, eve_coin.parent_coin_info);
2071
2072 assert_eq!(
2073 new_datastore.coin.parent_coin_info,
2074 datastore_from_launcher.coin.coin_id()
2075 );
2076 assert_eq!(
2077 new_datastore.coin.puzzle_hash,
2078 SingletonArgs::curry_tree_hash(
2079 datastore_from_launcher.info.launcher_id,
2080 CurriedProgram {
2081 program: TreeHash::new(NFT_STATE_LAYER_HASH),
2082 args: NftStateLayerArgs::<TreeHash, DataStoreMetadata> {
2083 mod_hash: NFT_STATE_LAYER_HASH.into(),
2084 metadata: new_metadata,
2085 metadata_updater_puzzle_hash: DL_METADATA_UPDATER_PUZZLE_HASH.into(),
2086 inner_puzzle: new_inner_ph.into(),
2087 },
2088 }
2089 .tree_hash()
2090 )
2091 .into()
2092 );
2093 assert_eq!(new_datastore.coin.amount, 1);
2094
2095 match new_datastore.proof {
2096 Proof::Lineage(proof) => {
2097 assert_eq!(proof.parent_parent_coin_info, eve_coin.parent_coin_info);
2098 assert_eq!(proof.parent_amount, eve_coin.amount);
2099 assert_eq!(
2100 proof.parent_inner_puzzle_hash,
2101 CurriedProgram {
2102 program: TreeHash::new(NFT_STATE_LAYER_HASH),
2103 args: NftStateLayerArgs::<TreeHash, DataStoreMetadata> {
2104 mod_hash: NFT_STATE_LAYER_HASH.into(),
2105 metadata: datastore_from_launcher.info.metadata,
2106 metadata_updater_puzzle_hash: DL_METADATA_UPDATER_PUZZLE_HASH.into(),
2107 inner_puzzle: owner_puzzle_hash,
2108 },
2109 }
2110 .tree_hash()
2111 .into()
2112 );
2113 }
2114 Proof::Eve(_) => panic!("expected lineage (not eve) proof for new_info"),
2115 }
2116
2117 ctx.insert(spend);
2118
2119 sim.spend_coins(ctx.take(), &[owner.sk, owner2.sk])?;
2120
2121 let eve_coin_state = sim
2122 .coin_state(eve_coin.coin_id())
2123 .expect("expected eve coin");
2124 assert!(eve_coin_state.created_height.is_some());
2125
2126 Ok(())
2127 }
2128}