chia_sdk_driver/layers/
bulletin_layer.rs

1use chia_sdk_types::{
2    puzzles::{IndexWrapperArgs, INDEX_WRAPPER_HASH},
3    Mod,
4};
5use clvm_traits::{FromClvm, MatchByte};
6use clvm_utils::{ToTreeHash, TreeHash};
7use clvmr::{Allocator, NodePtr};
8
9use crate::{DriverError, Layer, Puzzle, SpendContext};
10
11/// A standard nonce for bulletin coins to differentiate them from normal XCH coins.
12/// This is the CLVM value ("b", ()), where "b" stands for "bulletin" and the pair
13/// is to disambiguate it from standard atom nonces. This is not related to MIPS.
14pub type BulletinNonce = (MatchByte<98>, ());
15
16/// The Bulletin [`Layer`] is used to wrap a puzzle hash with a static nonce
17/// to differentiate it from normal XCH coins.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct BulletinLayer<I> {
20    /// The inner puzzle layer, used to identify and spend the bulletin.
21    pub inner_puzzle: I,
22}
23
24impl<I> BulletinLayer<I> {
25    pub fn new(inner_puzzle: I) -> Self {
26        Self { inner_puzzle }
27    }
28}
29
30impl<I> Layer for BulletinLayer<I>
31where
32    I: Layer,
33{
34    type Solution = I::Solution;
35
36    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
37        let Some(puzzle) = puzzle.as_curried() else {
38            return Ok(None);
39        };
40
41        if puzzle.mod_hash != INDEX_WRAPPER_HASH {
42            return Ok(None);
43        }
44
45        let args = IndexWrapperArgs::<NodePtr, NodePtr>::from_clvm(allocator, puzzle.args)?;
46
47        if BulletinNonce::from_clvm(allocator, args.nonce).is_err() {
48            return Err(DriverError::InvalidModHash);
49        }
50
51        let Some(inner_puzzle) =
52            I::parse_puzzle(allocator, Puzzle::parse(allocator, args.inner_puzzle))?
53        else {
54            return Ok(None);
55        };
56
57        Ok(Some(Self { inner_puzzle }))
58    }
59
60    fn parse_solution(
61        allocator: &Allocator,
62        solution: NodePtr,
63    ) -> Result<Self::Solution, DriverError> {
64        I::parse_solution(allocator, solution)
65    }
66
67    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
68        let inner_puzzle = self.inner_puzzle.construct_puzzle(ctx)?;
69        ctx.curry(IndexWrapperArgs::<BulletinNonce, _>::new(
70            (MatchByte, ()),
71            inner_puzzle,
72        ))
73    }
74
75    fn construct_solution(
76        &self,
77        ctx: &mut SpendContext,
78        solution: Self::Solution,
79    ) -> Result<NodePtr, DriverError> {
80        self.inner_puzzle.construct_solution(ctx, solution)
81    }
82}
83
84impl<I> ToTreeHash for BulletinLayer<I>
85where
86    I: ToTreeHash,
87{
88    fn tree_hash(&self) -> TreeHash {
89        let inner_puzzle_hash = self.inner_puzzle.tree_hash();
90        IndexWrapperArgs::<BulletinNonce, _>::new((MatchByte, ()), inner_puzzle_hash)
91            .curry_tree_hash()
92    }
93}