Skip to main content

chia_sdk_driver/primitives/datalayer/
datastore.rs

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/// Everything that is required to spend a [`Datastore`] coin.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct Datastore<M = DatastoreMetadata> {
35    /// The coin that holds this [`Datastore`].
36    pub coin: Coin,
37    /// The lineage proof for the singletonlayer.
38    pub proof: Proof,
39    /// The info associated with the [`Datastore`], including the metadata.
40    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    /// Creates a coin spend for this [`Datastore`].
52    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                // if running owner puzzle, the line below will return 'None', thus ensuring correct puzzle behavior
84                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    /// Returns the lineage proof that would be used by the child.
107    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
134// Does not implement Primitive because it needs extra info.
135impl<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            // no hints; owner puzzle hash is the inner puzzle hash
151            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            // vanilla store using old memo format
169            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            // we're just launching this singleton :)
233            // solution is (singleton_full_puzzle_hash amount key_value_list)
234            // kv_list is (metadata state_layer_hash)
235            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                        // datastore launched using old memo format
272                        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        // At this point, inner puzzle might be either a delegation layer or just an ownership layer.
322        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        // first, just compute new coin info - will be used in any case
362
363        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        // if the coin was re-created with memos, there is a delegation layer
385        // and delegated puzzles have been updated (we can rebuild the list from memos)
386
387        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            // keep in mind that there's always the launcher id memo being added
397            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        // does the parent coin currently have a delegation layer?
410        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            // to get more info, we'll need to run the delegated puzzle (delegation layer's "inner" puzzle)
428            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                // no CREATE_COIN was created by the innermost puzzle
444                // delegation layer therefore added one (assuming the spend is valid)]
445                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 there were any memos, the if above would have caught it since it processes
460            // output conditions of the state layer inner puzzle (i.e., it runs the delegation layer)
461            // therefore, this spend is either 'exiting' the delegation layer or re-creatign it
462            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                    // owner is re-creating the delegation layer with the same options
467                    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, // owner puzzle was ran
474                            delegated_puzzles: parent_delegated_puzzles.to_vec(),
475                        },
476                    }));
477                }
478
479                // owner is exiting the delegation layer
480                owner_puzzle_hash = create_coin.puzzle_hash;
481            }
482        }
483
484        // all methods exhausted; this coin doesn't seem to have a delegation layer
485        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                    // https://github.com/Chia-Network/clvm_rs/blob/66a17f9576d26011321bb4c8c16eb1c63b169f1f/src/allocator.rs#L295
525                    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    // As an owner use CREATE_COIN to:
541    //  - just re-create store (no hints needed)
542    //  - change delegated puzzles (hints needed)
543    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            // metadata updater will just return solution, so we can set the solution to NewMetadataOutput :)
587            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, // Default to None for existing tests
692        }
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        // Make sure the datastore was created.
736        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        // writer: update metadata
797        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        // admin: remove writer from delegated puzzles
821        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        // oracle: just spend :)
852
853        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        // mint a coin that asserts the announcement and has enough value
870        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        // finally, remove delegation layer altogether
883        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        // Make sure the datastore was created.
907        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 must have admin layer in this scenario
958        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        // transition from src to dst
1003        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        // delegated puzzle info + inner puzzle reveal + solution
1052        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        // transition from src to dst using owner puzzle
1189        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        // delegated puzzle info + inner puzzle reveal + solution
1233        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        // transition from src to dst using writer (update metadata)
1376        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        // 'dude' spends oracle
1500        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        // mint a coin that asserts the announcement and has enough value
1512        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        // asserts
1524
1525        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        // owner melts
1634        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        // asserts
1642
1643        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()); // tx happened
1664
1665        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        // delegated puzzle tries to steal the coin
1725        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        // attacker tries to melt the coin via delegated puzzle
1778        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        // launch using old memos scheme
1957        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        // https://github.com/Chia-Network/chia-blockchain/blob/4ffb6dfa6f53f6cd1920bcc775e27377a771fbec/chia/wallet/db_wallet/db_wallet_puzzles.py#L59
1975        // kv_list = 'memos': (root_hash inner_puzzle_hash)
1976        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        // now spend the signleton using old memo format and check that info is parsed correctly
2023
2024        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        // https://github.com/Chia-Network/chia-blockchain/blob/4ffb6dfa6f53f6cd1920bcc775e27377a771fbec/chia/data_layer/data_layer_wallet.py#L526
2043        // memos are (launcher_id root_hash inner_puzzle_hash)
2044        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}