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