chia_sdk_driver/layers/
cat_layer.rs

1use chia_protocol::Bytes32;
2use chia_puzzle_types::cat::{CatArgs, CatSolution};
3use chia_puzzles::CAT_PUZZLE_HASH;
4use clvm_traits::FromClvm;
5use clvm_utils::{ToTreeHash, TreeHash};
6use clvmr::{Allocator, NodePtr};
7
8use crate::{DriverError, Layer, Puzzle, SpendContext};
9
10/// The CAT [`Layer`] enforces restrictions on the supply of a token.
11/// Specifically, unless the TAIL program is run, the supply cannot change.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct CatLayer<I> {
14    /// The asset id of the CAT token. This is the tree hash of the TAIL program.
15    pub asset_id: Bytes32,
16    /// The inner puzzle layer, commonly used for determining ownership.
17    pub inner_puzzle: I,
18}
19
20impl<I> CatLayer<I> {
21    pub fn new(asset_id: Bytes32, inner_puzzle: I) -> Self {
22        Self {
23            asset_id,
24            inner_puzzle,
25        }
26    }
27}
28
29impl<I> Layer for CatLayer<I>
30where
31    I: Layer,
32{
33    type Solution = CatSolution<I::Solution>;
34
35    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
36        let Some(puzzle) = puzzle.as_curried() else {
37            return Ok(None);
38        };
39
40        if puzzle.mod_hash != CAT_PUZZLE_HASH.into() {
41            return Ok(None);
42        }
43
44        let args = CatArgs::<NodePtr>::from_clvm(allocator, puzzle.args)?;
45
46        if args.mod_hash != CAT_PUZZLE_HASH.into() {
47            return Err(DriverError::InvalidModHash);
48        }
49
50        let Some(inner_puzzle) =
51            I::parse_puzzle(allocator, Puzzle::parse(allocator, args.inner_puzzle))?
52        else {
53            return Ok(None);
54        };
55
56        Ok(Some(Self {
57            asset_id: args.asset_id,
58            inner_puzzle,
59        }))
60    }
61
62    fn parse_solution(
63        allocator: &Allocator,
64        solution: NodePtr,
65    ) -> Result<Self::Solution, DriverError> {
66        let solution = CatSolution::<NodePtr>::from_clvm(allocator, solution)?;
67        let inner_solution = I::parse_solution(allocator, solution.inner_puzzle_solution)?;
68        Ok(CatSolution {
69            inner_puzzle_solution: inner_solution,
70            lineage_proof: solution.lineage_proof,
71            prev_coin_id: solution.prev_coin_id,
72            this_coin_info: solution.this_coin_info,
73            next_coin_proof: solution.next_coin_proof,
74            prev_subtotal: solution.prev_subtotal,
75            extra_delta: solution.extra_delta,
76        })
77    }
78
79    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
80        let inner_puzzle = self.inner_puzzle.construct_puzzle(ctx)?;
81        ctx.curry(CatArgs::new(self.asset_id, inner_puzzle))
82    }
83
84    fn construct_solution(
85        &self,
86        ctx: &mut SpendContext,
87        solution: Self::Solution,
88    ) -> Result<NodePtr, DriverError> {
89        let inner_solution = self
90            .inner_puzzle
91            .construct_solution(ctx, solution.inner_puzzle_solution)?;
92        ctx.alloc(&CatSolution {
93            inner_puzzle_solution: inner_solution,
94            lineage_proof: solution.lineage_proof,
95            prev_coin_id: solution.prev_coin_id,
96            this_coin_info: solution.this_coin_info,
97            next_coin_proof: solution.next_coin_proof,
98            prev_subtotal: solution.prev_subtotal,
99            extra_delta: solution.extra_delta,
100        })
101    }
102}
103
104impl<I> ToTreeHash for CatLayer<I>
105where
106    I: ToTreeHash,
107{
108    fn tree_hash(&self) -> TreeHash {
109        let inner_puzzle_hash = self.inner_puzzle.tree_hash();
110        CatArgs::curry_tree_hash(self.asset_id, inner_puzzle_hash)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use chia_protocol::Coin;
117    use chia_puzzle_types::CoinProof;
118
119    use super::*;
120
121    #[test]
122    fn test_cat_layer() -> anyhow::Result<()> {
123        let mut ctx = SpendContext::new();
124        let asset_id = Bytes32::new([1; 32]);
125
126        let layer = CatLayer::new(asset_id, "Hello, world!".to_string());
127
128        let ptr = layer.construct_puzzle(&mut ctx)?;
129        let puzzle = Puzzle::parse(&ctx, ptr);
130        let roundtrip = CatLayer::<String>::parse_puzzle(&ctx, puzzle)?.expect("invalid CAT layer");
131
132        assert_eq!(roundtrip.asset_id, layer.asset_id);
133        assert_eq!(roundtrip.inner_puzzle, layer.inner_puzzle);
134
135        let expected = CatArgs::curry_tree_hash(asset_id, layer.inner_puzzle.tree_hash());
136        assert_eq!(hex::encode(ctx.tree_hash(ptr)), hex::encode(expected));
137
138        Ok(())
139    }
140
141    #[test]
142    fn test_cat_solution() -> anyhow::Result<()> {
143        let mut ctx = SpendContext::new();
144
145        let layer = CatLayer::new(Bytes32::default(), NodePtr::NIL);
146
147        let solution = CatSolution {
148            inner_puzzle_solution: NodePtr::NIL,
149            lineage_proof: None,
150            prev_coin_id: Bytes32::default(),
151            this_coin_info: Coin::new(Bytes32::default(), Bytes32::default(), 42),
152            next_coin_proof: CoinProof {
153                parent_coin_info: Bytes32::default(),
154                inner_puzzle_hash: Bytes32::default(),
155                amount: 34,
156            },
157            prev_subtotal: 0,
158            extra_delta: 0,
159        };
160        let expected_ptr = ctx.alloc(&solution)?;
161        let expected_hash = ctx.tree_hash(expected_ptr);
162
163        let actual_ptr = layer.construct_solution(&mut ctx, solution)?;
164        let actual_hash = ctx.tree_hash(actual_ptr);
165
166        assert_eq!(hex::encode(actual_hash), hex::encode(expected_hash));
167
168        let roundtrip = CatLayer::<NodePtr>::parse_solution(&ctx, actual_ptr)?;
169        assert_eq!(roundtrip, solution);
170
171        Ok(())
172    }
173}