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}