chia_sdk_driver/primitives/datalayer/
datastore.rs

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