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