Skip to main content

chia_sdk_driver/clear_signing/
inner_spend.rs

1use 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}