chia_sdk_driver/layers/action_layer/
precommit_layer.rs

1use chia_protocol::{Bytes, Bytes32};
2use chia_puzzles::SINGLETON_TOP_LAYER_V1_1_HASH;
3use chia_sdk_types::{
4    puzzles::{
5        AnyMetadataUpdater, CatNftMetadata, DefaultCatMakerArgs, PrecommitLayer1stCurryArgs,
6        PrecommitLayer2ndCurryArgs, PrecommitLayerSolution, PRECOMMIT_LAYER_PUZZLE_HASH,
7    },
8    Conditions, Mod,
9};
10use clvm_traits::{clvm_quote, clvm_tuple, ClvmEncoder, FromClvm, ToClvm, ToClvmError};
11use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
12use clvmr::{Allocator, NodePtr};
13
14use crate::{DriverError, Layer, Puzzle, SpendContext};
15
16#[derive(Debug, Clone)]
17#[must_use]
18pub struct PrecommitLayer<V> {
19    pub controller_singleton_struct_hash: Bytes32,
20    pub relative_block_height: u32,
21    pub payout_puzzle_hash: Bytes32,
22    pub refund_puzzle_hash: Bytes32,
23    pub value: V,
24}
25
26impl<V> PrecommitLayer<V> {
27    pub fn new(
28        controller_singleton_struct_hash: Bytes32,
29        relative_block_height: u32,
30        payout_puzzle_hash: Bytes32,
31        refund_puzzle_hash: Bytes32,
32        value: V,
33    ) -> Self {
34        Self {
35            controller_singleton_struct_hash,
36            relative_block_height,
37            payout_puzzle_hash,
38            refund_puzzle_hash,
39            value,
40        }
41    }
42
43    pub fn first_curry_hash(
44        controller_singleton_struct_hash: Bytes32,
45        relative_block_height: u32,
46        payout_puzzle_hash: Bytes32,
47    ) -> TreeHash {
48        PrecommitLayer1stCurryArgs {
49            singleton_mod_hash: SINGLETON_TOP_LAYER_V1_1_HASH.into(),
50            singleton_struct_hash: controller_singleton_struct_hash,
51            relative_block_height,
52            payout_puzzle_hash,
53        }
54        .curry_tree_hash()
55    }
56
57    pub fn puzzle_hash(
58        controller_singleton_struct_hash: Bytes32,
59        relative_block_height: u32,
60        payout_puzzle_hash: Bytes32,
61        refund_puzzle_hash: Bytes32,
62        value_hash: TreeHash,
63    ) -> TreeHash {
64        CurriedProgram {
65            program: Self::first_curry_hash(
66                controller_singleton_struct_hash,
67                relative_block_height,
68                payout_puzzle_hash,
69            ),
70            args: PrecommitLayer2ndCurryArgs {
71                refund_puzzle_hash,
72                value: value_hash,
73            },
74        }
75        .tree_hash()
76    }
77
78    pub fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError>
79    where
80        V: Clone + ToClvm<Allocator>,
81    {
82        let prog_1st_curry = ctx.curry(PrecommitLayer1stCurryArgs {
83            singleton_mod_hash: SINGLETON_TOP_LAYER_V1_1_HASH.into(),
84            singleton_struct_hash: self.controller_singleton_struct_hash,
85            relative_block_height: self.relative_block_height,
86            payout_puzzle_hash: self.payout_puzzle_hash,
87        })?;
88
89        Ok(CurriedProgram {
90            program: prog_1st_curry,
91            args: PrecommitLayer2ndCurryArgs {
92                refund_puzzle_hash: self.refund_puzzle_hash,
93                value: self.value.clone(),
94            },
95        }
96        .to_clvm(ctx)?)
97    }
98
99    pub fn construct_solution(
100        &self,
101        ctx: &mut SpendContext,
102        solution: PrecommitLayerSolution,
103    ) -> Result<NodePtr, DriverError> {
104        ctx.alloc(&solution)
105    }
106}
107
108impl<V> Layer for PrecommitLayer<V>
109where
110    V: ToClvm<Allocator> + FromClvm<Allocator> + Clone,
111{
112    type Solution = PrecommitLayerSolution;
113
114    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
115        let Some(puzzle_2nd_curry) = puzzle.as_curried() else {
116            return Ok(None);
117        };
118
119        let Some(curried) = CurriedProgram::<NodePtr, NodePtr>::parse_puzzle(allocator, puzzle)?
120        else {
121            return Ok(None);
122        };
123        let puzzle_1st_curry = Puzzle::parse(allocator, curried.program);
124        let Some(puzzle_1st_curry) = puzzle_1st_curry.as_curried() else {
125            return Ok(None);
126        };
127
128        if puzzle_1st_curry.mod_hash != PRECOMMIT_LAYER_PUZZLE_HASH {
129            return Ok(None);
130        }
131
132        let args_2nd_curry =
133            PrecommitLayer2ndCurryArgs::<V>::from_clvm(allocator, puzzle_2nd_curry.args)?;
134        let args_1st_curry =
135            PrecommitLayer1stCurryArgs::from_clvm(allocator, puzzle_1st_curry.args)?;
136
137        Ok(Some(Self {
138            controller_singleton_struct_hash: args_1st_curry.singleton_struct_hash,
139            relative_block_height: args_1st_curry.relative_block_height,
140            payout_puzzle_hash: args_1st_curry.payout_puzzle_hash,
141            refund_puzzle_hash: args_2nd_curry.refund_puzzle_hash,
142            value: args_2nd_curry.value,
143        }))
144    }
145
146    fn parse_solution(
147        allocator: &Allocator,
148        solution: NodePtr,
149    ) -> Result<Self::Solution, DriverError> {
150        PrecommitLayerSolution::from_clvm(allocator, solution).map_err(DriverError::FromClvm)
151    }
152
153    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
154        self.construct_puzzle(ctx)
155    }
156
157    fn construct_solution(
158        &self,
159        ctx: &mut SpendContext,
160        solution: Self::Solution,
161    ) -> Result<NodePtr, DriverError> {
162        self.construct_solution(ctx, solution)
163    }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub struct CatalogPrecommitValue<T = NodePtr, S = ()>
168where
169    S: ToTreeHash,
170{
171    pub tail_reveal: T,
172    pub initial_inner_puzzle_hash: Bytes32,
173    pub cat_maker_hash: Bytes32,
174    pub cat_maker_solution: S,
175}
176
177impl<T> CatalogPrecommitValue<T> {
178    pub fn with_default_cat_maker(
179        payment_asset_tail_hash_hash: TreeHash,
180        initial_inner_puzzle_hash: Bytes32,
181        tail_reveal: T,
182    ) -> Self {
183        Self {
184            tail_reveal,
185            initial_inner_puzzle_hash,
186            cat_maker_hash: DefaultCatMakerArgs::new(payment_asset_tail_hash_hash.into())
187                .curry_tree_hash()
188                .into(),
189            cat_maker_solution: (),
190        }
191    }
192
193    pub fn initial_inner_puzzle(
194        ctx: &mut SpendContext,
195        owner_inner_puzzle_hash: Bytes32,
196        initial_metadata: &CatNftMetadata,
197    ) -> Result<NodePtr, DriverError> {
198        let mut conds = Conditions::new().create_coin(
199            owner_inner_puzzle_hash,
200            1,
201            ctx.hint(owner_inner_puzzle_hash)?,
202        );
203        let updater_solution = ctx.alloc(&initial_metadata)?;
204        conds = conds.update_nft_metadata(ctx.alloc_mod::<AnyMetadataUpdater>()?, updater_solution);
205        conds = conds.remark(ctx.alloc(&"MEOW".to_string())?);
206
207        ctx.alloc(&clvm_quote!(conds))
208    }
209}
210
211// On-chain, the CATalog precommit value is just (TAIL . HASH)
212impl<N, E: ClvmEncoder<Node = N>, T, S> ToClvm<E> for CatalogPrecommitValue<T, S>
213where
214    S: ToTreeHash,
215    T: ToClvm<E> + Clone,
216{
217    fn to_clvm(&self, encoder: &mut E) -> Result<N, ToClvmError> {
218        let hash: Bytes32 = clvm_tuple!(
219            self.initial_inner_puzzle_hash,
220            clvm_tuple!(self.cat_maker_hash, self.cat_maker_solution.tree_hash())
221        )
222        .tree_hash()
223        .into();
224
225        clvm_tuple!(self.tail_reveal.clone(), hash).to_clvm(encoder)
226    }
227}
228
229// value is:
230// (c
231//   (c (c cat_maker_reveal cat_maker_solution) (c pricing_puzzle_reveal pricing_solution))
232//   (c (c secret handle) (c owner_launcher_id resolved_data)))
233// )
234#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct XchandlesPrecommitValue<CS = (), PS = TreeHash, S = Bytes32>
236where
237    CS: ToTreeHash,
238    PS: ToTreeHash,
239    S: ToTreeHash,
240{
241    pub cat_maker_hash: Bytes32,
242    pub cat_maker_solution: CS,
243    pub pricing_puzzle_hash: Bytes32,
244    pub pricing_solution: PS,
245    pub handle: String,
246    pub secret: S,
247    pub owner_launcher_id: Bytes32,
248    pub resolved_data: Bytes,
249}
250
251impl<CS, PS, S> XchandlesPrecommitValue<CS, PS, S>
252where
253    CS: ToTreeHash,
254    PS: ToTreeHash,
255    S: ToTreeHash,
256{
257    #[allow(clippy::too_many_arguments)]
258    pub fn new(
259        cat_maker_hash: Bytes32,
260        cat_maker_solution: CS,
261        pricing_puzzle_hash: Bytes32,
262        pricing_solution: PS,
263        handle: String,
264        secret: S,
265        owner_launcher_id: Bytes32,
266        resolved_data: Bytes,
267    ) -> Self {
268        Self {
269            cat_maker_hash,
270            cat_maker_solution,
271            pricing_puzzle_hash,
272            pricing_solution,
273            handle,
274            secret,
275            owner_launcher_id,
276            resolved_data,
277        }
278    }
279}
280
281impl XchandlesPrecommitValue<(), TreeHash, Bytes32> {
282    pub fn for_normal_registration<PS>(
283        payment_tail_hash_hash: TreeHash,
284        pricing_puzzle_hash: TreeHash,
285        pricing_puzzle_solution: &PS,
286        handle: String,
287        secret: Bytes32,
288        owner_launcher_id: Bytes32,
289        resolved_data: Bytes,
290    ) -> Self
291    where
292        PS: ToTreeHash,
293    {
294        Self::new(
295            DefaultCatMakerArgs::new(payment_tail_hash_hash.into())
296                .curry_tree_hash()
297                .into(),
298            (),
299            pricing_puzzle_hash.into(),
300            pricing_puzzle_solution.tree_hash(),
301            handle,
302            secret,
303            owner_launcher_id,
304            resolved_data,
305        )
306    }
307}
308
309// On-chain, the precommit value is just a hash of the data it stores
310impl<N, E: ClvmEncoder<Node = N>, CS, PS, S> ToClvm<E> for XchandlesPrecommitValue<CS, PS, S>
311where
312    CS: ToTreeHash,
313    PS: ToTreeHash,
314    S: ToTreeHash,
315{
316    fn to_clvm(&self, encoder: &mut E) -> Result<N, ToClvmError> {
317        let data_hash: Bytes32 = clvm_tuple!(
318            clvm_tuple!(
319                clvm_tuple!(self.cat_maker_hash, self.cat_maker_solution.tree_hash()),
320                clvm_tuple!(self.pricing_puzzle_hash, self.pricing_solution.tree_hash())
321            ),
322            clvm_tuple!(
323                clvm_tuple!(self.handle.tree_hash(), self.secret.tree_hash()),
324                clvm_tuple!(self.owner_launcher_id, self.resolved_data.tree_hash())
325            )
326        )
327        .tree_hash()
328        .into();
329
330        data_hash.to_clvm(encoder)
331    }
332}