chia_sdk_driver/layers/
p2_singleton_layer.rs

1use chia_protocol::{Bytes32, Coin};
2use chia_puzzles::{P2_SINGLETON_HASH, SINGLETON_LAUNCHER_HASH, SINGLETON_TOP_LAYER_V1_1_HASH};
3use chia_sdk_types::puzzles::{P2SingletonArgs, P2SingletonSolution};
4use clvm_traits::FromClvm;
5use clvm_utils::{ToTreeHash, TreeHash};
6use clvmr::{Allocator, NodePtr};
7
8use crate::{DriverError, Layer, Puzzle, Spend, SpendContext};
9
10/// The p2 singleton [`Layer`] allows for requiring that a
11/// singleton be spent alongside this coin to authorize it.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct P2SingletonLayer {
14    pub launcher_id: Bytes32,
15}
16
17impl P2SingletonLayer {
18    pub fn new(launcher_id: Bytes32) -> Self {
19        Self { launcher_id }
20    }
21
22    pub fn spend(
23        &self,
24        ctx: &mut SpendContext,
25        coin_id: Bytes32,
26        singleton_inner_puzzle_hash: Bytes32,
27    ) -> Result<Spend, DriverError> {
28        let puzzle = self.construct_puzzle(ctx)?;
29        let solution = self.construct_solution(
30            ctx,
31            P2SingletonSolution {
32                singleton_inner_puzzle_hash,
33                my_id: coin_id,
34            },
35        )?;
36        Ok(Spend { puzzle, solution })
37    }
38
39    pub fn spend_coin(
40        &self,
41        ctx: &mut SpendContext,
42        coin: Coin,
43        singleton_inner_puzzle_hash: Bytes32,
44    ) -> Result<(), DriverError> {
45        let coin_spend = self.construct_coin_spend(
46            ctx,
47            coin,
48            P2SingletonSolution {
49                singleton_inner_puzzle_hash,
50                my_id: coin.coin_id(),
51            },
52        )?;
53        ctx.insert(coin_spend);
54        Ok(())
55    }
56}
57
58impl Layer for P2SingletonLayer {
59    type Solution = P2SingletonSolution;
60
61    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
62        let Some(puzzle) = puzzle.as_curried() else {
63            return Ok(None);
64        };
65
66        if puzzle.mod_hash != P2_SINGLETON_HASH.into() {
67            return Ok(None);
68        }
69
70        let args = P2SingletonArgs::from_clvm(allocator, puzzle.args)?;
71
72        if args.singleton_mod_hash != SINGLETON_TOP_LAYER_V1_1_HASH.into()
73            || args.launcher_puzzle_hash != SINGLETON_LAUNCHER_HASH.into()
74        {
75            return Err(DriverError::InvalidSingletonStruct);
76        }
77
78        Ok(Some(Self {
79            launcher_id: args.launcher_id,
80        }))
81    }
82
83    fn parse_solution(
84        allocator: &Allocator,
85        solution: NodePtr,
86    ) -> Result<Self::Solution, DriverError> {
87        Ok(P2SingletonSolution::from_clvm(allocator, solution)?)
88    }
89
90    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
91        ctx.curry(P2SingletonArgs::new(self.launcher_id))
92    }
93
94    fn construct_solution(
95        &self,
96        ctx: &mut SpendContext,
97        solution: Self::Solution,
98    ) -> Result<NodePtr, DriverError> {
99        ctx.alloc(&solution)
100    }
101}
102
103impl ToTreeHash for P2SingletonLayer {
104    fn tree_hash(&self) -> TreeHash {
105        P2SingletonArgs::curry_tree_hash(self.launcher_id)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use chia_protocol::Coin;
112    use chia_puzzle_types::{singleton::SingletonSolution, EveProof, Proof};
113    use chia_sdk_test::Simulator;
114    use chia_sdk_types::Conditions;
115
116    use super::*;
117
118    use crate::{Launcher, SingletonLayer, SpendWithConditions, StandardLayer};
119
120    #[test]
121    fn test_p2_singleton_layer() -> anyhow::Result<()> {
122        let mut sim = Simulator::default();
123        let ctx = &mut SpendContext::new();
124
125        let alice = sim.bls(2);
126        let p2 = StandardLayer::new(alice.pk);
127
128        let launcher = Launcher::new(alice.coin.coin_id(), 1);
129        let launcher_id = launcher.coin().coin_id();
130        let (create_singleton, singleton) = launcher.spend(ctx, alice.puzzle_hash, ())?;
131
132        let p2_singleton = P2SingletonLayer::new(launcher_id);
133        let p2_singleton_hash = p2_singleton.tree_hash().into();
134
135        let memos = ctx.hint(launcher_id)?;
136        p2.spend(
137            ctx,
138            alice.coin,
139            create_singleton.create_coin(p2_singleton_hash, 1, memos),
140        )?;
141
142        let p2_coin = Coin::new(alice.coin.coin_id(), p2_singleton_hash, 1);
143        p2_singleton.spend_coin(ctx, p2_coin, alice.puzzle_hash)?;
144
145        let memos = ctx.hint(launcher_id)?;
146        let inner_solution = p2
147            .spend_with_conditions(
148                ctx,
149                Conditions::new()
150                    .create_coin(alice.puzzle_hash, 1, memos)
151                    .create_puzzle_announcement(p2_coin.coin_id().into()),
152            )?
153            .solution;
154        let singleton_spend = SingletonLayer::new(launcher_id, p2.construct_puzzle(ctx)?)
155            .construct_coin_spend(
156                ctx,
157                singleton,
158                SingletonSolution {
159                    lineage_proof: Proof::Eve(EveProof {
160                        parent_parent_coin_info: alice.coin.coin_id(),
161                        parent_amount: 1,
162                    }),
163                    amount: singleton.amount,
164                    inner_solution,
165                },
166            )?;
167        ctx.insert(singleton_spend);
168
169        sim.spend_coins(ctx.take(), &[alice.sk])?;
170
171        Ok(())
172    }
173}