chia_sdk_driver/layers/
streaming_layer.rs

1use std::borrow::Cow;
2
3use chia_protocol::Bytes32;
4use chia_sdk_types::Mod;
5use clvm_traits::{FromClvm, ToClvm};
6use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
7use clvmr::{Allocator, NodePtr};
8use hex_literal::hex;
9
10use crate::{CurriedPuzzle, DriverError, Layer, Puzzle, SpendContext};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct StreamLayer {
14    pub recipient: Bytes32,
15    pub clawback_ph: Option<Bytes32>,
16    pub end_time: u64,
17    pub last_payment_time: u64,
18}
19
20impl StreamLayer {
21    pub fn new(
22        recipient: Bytes32,
23        clawback_ph: Option<Bytes32>,
24        end_time: u64,
25        last_payment_time: u64,
26    ) -> Self {
27        Self {
28            recipient,
29            clawback_ph,
30            end_time,
31            last_payment_time,
32        }
33    }
34
35    pub fn puzzle_hash(&self) -> TreeHash {
36        StreamPuzzle2ndCurryArgs::curry_tree_hash(
37            self.recipient,
38            self.clawback_ph,
39            self.end_time,
40            self.last_payment_time,
41        )
42    }
43}
44
45impl Layer for StreamLayer {
46    type Solution = StreamPuzzleSolution;
47
48    fn parse_puzzle(
49        allocator: &Allocator,
50        puzzle_2nd_curry: Puzzle,
51    ) -> Result<Option<Self>, DriverError> {
52        let Some(puzzle_2nd_curry) = puzzle_2nd_curry.as_curried() else {
53            return Ok(None);
54        };
55
56        let Ok(program_2nd_curry) =
57            CurriedProgram::<NodePtr, NodePtr>::from_clvm(allocator, puzzle_2nd_curry.curried_ptr)
58        else {
59            return Ok(None);
60        };
61        let Some(puzzle_1st_curry) = CurriedPuzzle::parse(allocator, program_2nd_curry.program)
62        else {
63            return Ok(None);
64        };
65
66        let Ok(args1) = StreamPuzzle1stCurryArgs::from_clvm(allocator, puzzle_1st_curry.args)
67        else {
68            return Ok(None);
69        };
70        let Ok(args2) = StreamPuzzle2ndCurryArgs::from_clvm(allocator, puzzle_2nd_curry.args)
71        else {
72            return Ok(None);
73        };
74
75        if puzzle_1st_curry.mod_hash != STREAM_PUZZLE_HASH {
76            return Err(DriverError::InvalidModHash);
77        }
78
79        Ok(Some(Self {
80            recipient: args1.recipient,
81            clawback_ph: args1.clawback_ph,
82            end_time: args1.end_time,
83            last_payment_time: args2.last_payment_time,
84        }))
85    }
86
87    fn parse_solution(
88        allocator: &Allocator,
89        solution: NodePtr,
90    ) -> Result<Self::Solution, DriverError> {
91        StreamPuzzleSolution::from_clvm(allocator, solution).map_err(DriverError::FromClvm)
92    }
93
94    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
95        let puzzle_1st_curry = ctx.curry(StreamPuzzle1stCurryArgs::new(
96            self.recipient,
97            self.clawback_ph,
98            self.end_time,
99        ))?;
100        let self_hash = StreamPuzzle1stCurryArgs::curry_tree_hash(
101            self.recipient,
102            self.clawback_ph,
103            self.end_time,
104        );
105
106        ctx.alloc(&CurriedProgram {
107            program: puzzle_1st_curry,
108            args: StreamPuzzle2ndCurryArgs::new(self_hash.into(), self.last_payment_time),
109        })
110    }
111
112    fn construct_solution(
113        &self,
114        ctx: &mut SpendContext,
115        solution: Self::Solution,
116    ) -> Result<NodePtr, DriverError> {
117        ctx.alloc(&solution)
118    }
119}
120
121pub const STREAM_PUZZLE: [u8; 587] =
122    hex!("ff02ffff01ff02ffff03ffff09ff8202ffffff05ffff14ffff12ff81bfffff11ff82017fff5f8080ffff11ff17ff5f80808080ffff01ff04ffff04ff18ffff04ff81bfff808080ffff04ffff03ff8203ffffff04ff10ffff04ff82017fff808080ffff04ff14ffff04ff82017fff80808080ffff04ffff03ffff09ff8202ffff8080ffff04ff1aff8080ffff04ff1cffff04ff05ffff04ff8202ffffff04ffff04ff05ff8080ff808080808080ffff04ffff03ffff09ff81bfff8202ff80ffff04ff1aff8080ffff04ff1cffff04ffff03ff8203ffff0bffff0bff5effff0bff16ffff0bff16ff6eff2f80ffff0bff16ffff0bff7effff0bff16ffff0bff16ff6effff0bffff0101ff2f8080ffff0bff16ffff0bff7effff0bff16ffff0bff16ff6effff0bffff0101ff82017f8080ffff0bff16ff6eff4e808080ff4e808080ff4e80808080ffff04ffff11ff81bfff8202ff80ffff04ffff04ffff0bffff0173ff0580ff8080ff808080808080ffff04ffff04ff12ffff04ffff0117ffff04ff82017fffff04ffff03ff8203ffff0bff0580ff8080808080ff808080808080ffff01ff088080ff0180ffff04ffff01ffffff5549ff5133ffff4301ff02ffffa04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459aa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ffa102a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222a102a8d5dd63fba471ebcb1f3e8f7c1e1879b7152a6e7298a91ce119a63400ade7c5ff018080");
123pub const STREAM_PUZZLE_HASH: TreeHash = TreeHash::new(hex!(
124    "e0e312a612aa14357e225c0dc21d351610c2377efab14406da6c7424d48feff8"
125));
126
127#[derive(ToClvm, FromClvm, Debug, Clone, Copy, PartialEq, Eq)]
128#[clvm(curry)]
129pub struct StreamPuzzle1stCurryArgs {
130    pub recipient: Bytes32,
131    pub clawback_ph: Option<Bytes32>,
132    pub end_time: u64,
133}
134
135impl StreamPuzzle1stCurryArgs {
136    pub fn new(recipient: Bytes32, clawback_ph: Option<Bytes32>, end_time: u64) -> Self {
137        Self {
138            recipient,
139            clawback_ph,
140            end_time,
141        }
142    }
143
144    pub fn curry_tree_hash(
145        recipient: Bytes32,
146        clawback_ph: Option<Bytes32>,
147        end_time: u64,
148    ) -> TreeHash {
149        CurriedProgram {
150            program: STREAM_PUZZLE_HASH,
151            args: StreamPuzzle1stCurryArgs::new(recipient, clawback_ph, end_time),
152        }
153        .tree_hash()
154    }
155}
156
157#[derive(ToClvm, FromClvm, Debug, Clone, Copy, PartialEq, Eq)]
158#[clvm(curry)]
159pub struct StreamPuzzle2ndCurryArgs {
160    pub self_hash: Bytes32,
161    pub last_payment_time: u64,
162}
163
164impl StreamPuzzle2ndCurryArgs {
165    pub fn new(self_hash: Bytes32, last_payment_time: u64) -> Self {
166        Self {
167            self_hash,
168            last_payment_time,
169        }
170    }
171
172    pub fn curry_tree_hash(
173        recipient: Bytes32,
174        clawback_ph: Option<Bytes32>,
175        end_time: u64,
176        last_payment_time: u64,
177    ) -> TreeHash {
178        let self_hash = StreamPuzzle1stCurryArgs::curry_tree_hash(recipient, clawback_ph, end_time);
179        CurriedProgram {
180            program: self_hash,
181            args: StreamPuzzle2ndCurryArgs::new(self_hash.into(), last_payment_time),
182        }
183        .tree_hash()
184    }
185}
186
187#[derive(ToClvm, FromClvm, Debug, Clone, PartialEq, Copy, Eq)]
188#[clvm(list)]
189pub struct StreamPuzzleSolution {
190    pub my_amount: u64,
191    pub payment_time: u64,
192    pub to_pay: u64,
193    #[clvm(rest)]
194    pub clawback: bool,
195}
196
197impl Mod for StreamPuzzle1stCurryArgs {
198    fn mod_reveal() -> Cow<'static, [u8]> {
199        Cow::Borrowed(&STREAM_PUZZLE)
200    }
201
202    fn mod_hash() -> TreeHash {
203        STREAM_PUZZLE_HASH
204    }
205}