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] = hex!(
122    "ff02ffff01ff02ffff03ffff09ff8202ffffff05ffff14ffff12ff81bfffff11ff82017fff5f8080ffff11ff17ff5f80808080ffff01ff04ffff04ff18ffff04ff81bfff808080ffff04ffff03ff8203ffffff04ff10ffff04ff82017fff808080ffff04ff14ffff04ff82017fff80808080ffff04ffff03ffff09ff8202ffff8080ffff04ff1aff8080ffff04ff1cffff04ff05ffff04ff8202ffffff04ffff04ff05ff8080ff808080808080ffff04ffff03ffff09ff81bfff8202ff80ffff04ff1aff8080ffff04ff1cffff04ffff03ff8203ffff0bffff0bff5effff0bff16ffff0bff16ff6eff2f80ffff0bff16ffff0bff7effff0bff16ffff0bff16ff6effff0bffff0101ff2f8080ffff0bff16ffff0bff7effff0bff16ffff0bff16ff6effff0bffff0101ff82017f8080ffff0bff16ff6eff4e808080ff4e808080ff4e80808080ffff04ffff11ff81bfff8202ff80ffff04ffff04ffff0bffff0173ff0580ff8080ff808080808080ffff04ffff04ff12ffff04ffff0117ffff04ff82017fffff04ffff03ff8203ffff0bff0580ff8080808080ff808080808080ffff01ff088080ff0180ffff04ffff01ffffff5549ff5133ffff4301ff02ffffa04bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459aa09dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2ffa102a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222a102a8d5dd63fba471ebcb1f3e8f7c1e1879b7152a6e7298a91ce119a63400ade7c5ff018080"
123);
124pub const STREAM_PUZZLE_HASH: TreeHash = TreeHash::new(hex!(
125    "e0e312a612aa14357e225c0dc21d351610c2377efab14406da6c7424d48feff8"
126));
127
128#[derive(ToClvm, FromClvm, Debug, Clone, Copy, PartialEq, Eq)]
129#[clvm(curry)]
130pub struct StreamPuzzle1stCurryArgs {
131    pub recipient: Bytes32,
132    pub clawback_ph: Option<Bytes32>,
133    pub end_time: u64,
134}
135
136impl StreamPuzzle1stCurryArgs {
137    pub fn new(recipient: Bytes32, clawback_ph: Option<Bytes32>, end_time: u64) -> Self {
138        Self {
139            recipient,
140            clawback_ph,
141            end_time,
142        }
143    }
144
145    pub fn curry_tree_hash(
146        recipient: Bytes32,
147        clawback_ph: Option<Bytes32>,
148        end_time: u64,
149    ) -> TreeHash {
150        CurriedProgram {
151            program: STREAM_PUZZLE_HASH,
152            args: StreamPuzzle1stCurryArgs::new(recipient, clawback_ph, end_time),
153        }
154        .tree_hash()
155    }
156}
157
158#[derive(ToClvm, FromClvm, Debug, Clone, Copy, PartialEq, Eq)]
159#[clvm(curry)]
160pub struct StreamPuzzle2ndCurryArgs {
161    pub self_hash: Bytes32,
162    pub last_payment_time: u64,
163}
164
165impl StreamPuzzle2ndCurryArgs {
166    pub fn new(self_hash: Bytes32, last_payment_time: u64) -> Self {
167        Self {
168            self_hash,
169            last_payment_time,
170        }
171    }
172
173    pub fn curry_tree_hash(
174        recipient: Bytes32,
175        clawback_ph: Option<Bytes32>,
176        end_time: u64,
177        last_payment_time: u64,
178    ) -> TreeHash {
179        let self_hash = StreamPuzzle1stCurryArgs::curry_tree_hash(recipient, clawback_ph, end_time);
180        CurriedProgram {
181            program: self_hash,
182            args: StreamPuzzle2ndCurryArgs::new(self_hash.into(), last_payment_time),
183        }
184        .tree_hash()
185    }
186}
187
188#[derive(ToClvm, FromClvm, Debug, Clone, PartialEq, Copy, Eq)]
189#[clvm(list)]
190pub struct StreamPuzzleSolution {
191    pub my_amount: u64,
192    pub payment_time: u64,
193    pub to_pay: u64,
194    #[clvm(rest)]
195    pub clawback: bool,
196}
197
198impl Mod for StreamPuzzle1stCurryArgs {
199    fn mod_reveal() -> Cow<'static, [u8]> {
200        Cow::Borrowed(&STREAM_PUZZLE)
201    }
202
203    fn mod_hash() -> TreeHash {
204        STREAM_PUZZLE_HASH
205    }
206}