chia_sdk_driver/primitives/datalayer/
datastore.rs

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