chia_sdk_driver/clear_signing/
inner_spend.rs1use chia_protocol::Bytes32;
2use chia_sdk_types::{
3 Condition,
4 puzzles::{DelegatedPuzzleFeederSolution, OneOfNSolution, SingletonMemberSolution},
5};
6use clvm_traits::FromClvm;
7use clvmr::{Allocator, NodePtr};
8
9use crate::{
10 AugmentedConditionLayer, ClawbackV2, DelegatedPuzzleFeederLayer, DriverError,
11 IndexWrapperLayer, Layer, P2OneOfManyLayer, Puzzle, RevealedP2Puzzle, Reveals,
12 SingletonMemberLayer, Spend, parse_delegated_spend,
13};
14
15#[derive(Debug, Clone)]
16pub struct InnerSpend {
17 pub clawback: Option<ClawbackInfo>,
18 pub custody: Option<CustodyInfo>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct ClawbackInfo {
23 pub clawback: ClawbackV2,
24 pub path: ClawbackPath,
25}
26
27impl ClawbackInfo {
28 pub fn new(clawback: ClawbackV2, path: ClawbackPath) -> Self {
29 Self { clawback, path }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ClawbackPath {
35 Sender,
36 Receiver,
37 PushThrough,
38}
39
40#[derive(Debug, Clone)]
41pub enum CustodyInfo {
42 P2Singleton(P2SingletonInfo),
43 DelegatedConditions(Vec<Condition>),
44 P2ConditionsOrSingleton(P2ConditionsOrSingletonInfo),
45}
46
47impl CustodyInfo {
48 pub fn receives_message(&self) -> bool {
49 matches!(
50 self,
51 Self::P2Singleton(_) | Self::P2ConditionsOrSingleton(_)
52 )
53 }
54}
55
56#[derive(Debug, Clone)]
57pub struct P2SingletonInfo {
58 pub launcher_id: Bytes32,
59 pub nonce: usize,
60 pub conditions: Vec<Condition>,
61 pub p2_puzzle_hash: Bytes32,
62}
63
64#[derive(Debug, Clone)]
65pub struct P2ConditionsOrSingletonInfo {
66 pub launcher_id: Bytes32,
67 pub nonce: usize,
68 pub conditions: Vec<Condition>,
69 pub p2_puzzle_hash: Bytes32,
70}
71
72type P2SingletonLayers = IndexWrapperLayer<usize, DelegatedPuzzleFeederLayer<SingletonMemberLayer>>;
73
74pub fn parse_inner_spend(
75 reveals: &Reveals,
76 allocator: &Allocator,
77 puzzle: Puzzle,
78 solution: NodePtr,
79) -> Result<InnerSpend, DriverError> {
80 let p2_puzzle_hash = puzzle.curried_puzzle_hash().into();
81
82 if let Some(puzzle) = P2SingletonLayers::parse_puzzle(allocator, puzzle)? {
83 let solution = P2SingletonLayers::parse_solution(allocator, solution)?;
84 let delegated_spend = Spend::new(solution.delegated_puzzle, solution.delegated_solution);
85 let conditions = parse_delegated_spend(allocator, delegated_spend)?;
86
87 Ok(InnerSpend {
88 clawback: None,
89 custody: Some(CustodyInfo::P2Singleton(P2SingletonInfo {
90 launcher_id: puzzle.inner_puzzle.inner_puzzle.launcher_id,
91 nonce: puzzle.nonce,
92 conditions,
93 p2_puzzle_hash,
94 })),
95 })
96 } else if let Some(p2_puzzle) = reveals.p2_puzzle(puzzle.curried_puzzle_hash()) {
97 match p2_puzzle {
98 RevealedP2Puzzle::Clawback(clawback) => {
99 let solution = P2OneOfManyLayer::parse_solution(allocator, solution)?;
100 let merkle_tree = clawback.merkle_tree();
101
102 let mut spent_leaf = None;
103
104 for leaf in merkle_tree.leaves() {
105 let proof = merkle_tree
106 .proof(leaf)
107 .expect("merkle tree proof should exist");
108
109 if solution.merkle_proof == proof {
110 spent_leaf = Some(leaf);
111 break;
112 }
113 }
114
115 let Some(spent_leaf) = spent_leaf else {
116 return Err(DriverError::InvalidMerkleProof);
117 };
118
119 let path = if spent_leaf == clawback.sender_path_hash() {
120 ClawbackPath::Sender
121 } else if spent_leaf == clawback.receiver_path_hash() {
122 ClawbackPath::Receiver
123 } else {
124 ClawbackPath::PushThrough
125 };
126
127 let mut result = InnerSpend {
128 clawback: Some(ClawbackInfo {
129 clawback: *clawback,
130 path,
131 }),
132 custody: None,
133 };
134
135 let solution_puzzle = Puzzle::parse(allocator, solution.puzzle);
136
137 if path != ClawbackPath::PushThrough
138 && let Some(augmented_condition_puzzle) =
139 AugmentedConditionLayer::<NodePtr, Puzzle>::parse_puzzle(
140 allocator,
141 solution_puzzle,
142 )?
143 {
144 let augmented_condition_solution =
145 AugmentedConditionLayer::<NodePtr, Puzzle>::parse_solution(
146 allocator,
147 solution.solution,
148 )?;
149
150 let inner_spend = parse_inner_spend(
151 reveals,
152 allocator,
153 augmented_condition_puzzle.inner_puzzle,
154 augmented_condition_solution.inner_solution,
155 )?;
156
157 if inner_spend.clawback.is_some() {
158 return Err(DriverError::NestedClawback);
159 }
160
161 result.custody = inner_spend.custody;
162 }
163
164 Ok(result)
165 }
166 RevealedP2Puzzle::P2ConditionsOrSingleton(info) => {
167 let solution = DelegatedPuzzleFeederSolution::<
168 NodePtr,
169 NodePtr,
170 OneOfNSolution<NodePtr, SingletonMemberSolution>,
171 >::from_clvm(allocator, solution)?;
172
173 let delegated_spend =
174 Spend::new(solution.delegated_puzzle, solution.delegated_solution);
175 let conditions = parse_delegated_spend(allocator, delegated_spend)?;
176
177 Ok(InnerSpend {
178 clawback: None,
179 custody: Some(CustodyInfo::P2ConditionsOrSingleton(
180 P2ConditionsOrSingletonInfo {
181 launcher_id: info.launcher_id,
182 nonce: info.nonce,
183 conditions,
184 p2_puzzle_hash,
185 },
186 )),
187 })
188 }
189 }
190 } else if let Ok(conditions) =
191 parse_delegated_spend(allocator, Spend::new(puzzle.ptr(), solution))
192 {
193 Ok(InnerSpend {
194 clawback: None,
195 custody: Some(CustodyInfo::DelegatedConditions(conditions)),
196 })
197 } else {
198 Ok(InnerSpend {
199 clawback: None,
200 custody: None,
201 })
202 }
203}