chia_sdk_driver/layers/
cat_layer.rs1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct CatLayer<I> {
14 pub asset_id: Bytes32,
16 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}