chia_sdk_driver/layers/
p2_singleton_layer.rs1use 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#[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}