1use std::convert::TryInto;
19use std::str::FromStr;
20
21use solana_program::{
22 account_info::{next_account_info, AccountInfo},
23 entrypoint,
24 entrypoint::ProgramResult,
25 msg,
26 native_token::lamports_to_sol,
27 program::invoke,
28 program_error::ProgramError,
29 pubkey::Pubkey,
30 system_instruction,
31 sysvar::{clock::Clock, fees::Fees, rent::Rent, Sysvar},
32};
33
34#[repr(C)]
36pub struct StreamFlow {
37 pub start_time: u64,
39 pub end_time: u64,
41 pub amount: u64,
43 pub withdrawn: u64,
45 pub sender: [u8; 32],
47 pub recipient: [u8; 32],
49}
50
51pub unsafe fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
56 ::std::slice::from_raw_parts((p as *const T) as *const u8, ::std::mem::size_of::<T>())
57}
58
59pub fn unpack_init_instruction(ix: &[u8], alice: &Pubkey, bob: &Pubkey) -> StreamFlow {
62 StreamFlow {
63 start_time: u64::from(u32::from_le_bytes(ix[1..5].try_into().unwrap())),
64 end_time: u64::from(u32::from_le_bytes(ix[5..9].try_into().unwrap())),
65 amount: u64::from_le_bytes(ix[9..17].try_into().unwrap()),
66 withdrawn: 0,
67 sender: alice.to_bytes(),
68 recipient: bob.to_bytes(),
69 }
70}
71
72pub fn unpack_account_data(ix: &[u8]) -> StreamFlow {
75 StreamFlow {
76 start_time: u64::from_le_bytes(ix[0..8].try_into().unwrap()),
77 end_time: u64::from_le_bytes(ix[8..16].try_into().unwrap()),
78 amount: u64::from_le_bytes(ix[16..24].try_into().unwrap()),
79 withdrawn: u64::from_le_bytes(ix[24..32].try_into().unwrap()),
80 sender: ix[32..64].try_into().unwrap(),
81 recipient: ix[64..96].try_into().unwrap(),
82 }
83}
84
85fn calculate_streamed(now: u64, start: u64, end: u64, amount: u64) -> u64 {
86 (((now - start) as f64) / ((end - start) as f64) * amount as f64) as u64
90}
91
92fn initialize_stream(pid: &Pubkey, accounts: &[AccountInfo], ix: &[u8]) -> ProgramResult {
93 msg!("Requested stream initialization");
94 let account_info_iter = &mut accounts.iter();
95 let alice = next_account_info(account_info_iter)?;
96 let bob = next_account_info(account_info_iter)?;
97 let pda = next_account_info(account_info_iter)?;
98 let system_program = next_account_info(account_info_iter)?;
99
100 if ix.len() != 17 {
101 return Err(ProgramError::InvalidInstructionData);
102 }
103
104 if !pda.data_is_empty() {
105 return Err(ProgramError::AccountAlreadyInitialized);
106 }
107
108 if !alice.is_writable
109 || !bob.is_writable
110 || !pda.is_writable
111 || !alice.is_signer
112 || !pda.is_signer
113 {
114 return Err(ProgramError::MissingRequiredSignature);
115 }
116
117 let mut sf = unpack_init_instruction(ix, alice.key, bob.key);
118 let struct_size = std::mem::size_of::<StreamFlow>();
119
120 let cluster_rent = Rent::get()?;
124 if alice.lamports() < sf.amount + cluster_rent.minimum_balance(struct_size) {
125 msg!("Not enough funds in sender's account to initialize stream");
126 return Err(ProgramError::InsufficientFunds);
127 }
128
129 let now = Clock::get()?.unix_timestamp as u64;
130 if sf.start_time < now || sf.start_time >= sf.end_time {
131 msg!("Timestamps are invalid!");
132 msg!("Solana cluster time: {}", now);
133 msg!("Stream start time: {}", sf.start_time);
134 msg!("Stream end time: {}", sf.end_time);
135 msg!("Stream duration: {}", sf.end_time - sf.start_time);
136 return Err(ProgramError::InvalidArgument);
137 }
138
139 invoke(
141 &system_instruction::create_account(
142 &alice.key,
143 &pda.key,
144 sf.amount + cluster_rent.minimum_balance(struct_size),
145 struct_size as u64,
146 &pid,
147 ),
148 &[alice.clone(), pda.clone(), system_program.clone()],
149 )?;
150
151 let fees = Fees::get()?;
154 **pda.try_borrow_mut_lamports()? -= fees.fee_calculator.lamports_per_signature * 2;
155 **bob.try_borrow_mut_lamports()? += fees.fee_calculator.lamports_per_signature * 2;
156 sf.withdrawn += fees.fee_calculator.lamports_per_signature * 2;
157
158 let mut data = pda.try_borrow_mut_data()?;
160 let bytes: &[u8] = unsafe { any_as_u8_slice(&sf) };
161 data[0..bytes.len()].clone_from_slice(bytes);
162
163 msg!(
164 "Successfully initialized {} SOL ({} lamports) stream for: {}",
165 lamports_to_sol(sf.amount),
166 sf.amount,
167 bob.key
168 );
169 msg!("Called by account: {}", alice.key);
170 msg!("Funds locked in account: {}", pda.key);
171 msg!("Stream duration: {} seconds", sf.end_time - sf.start_time);
172
173 Ok(())
174}
175
176fn withdraw_unlocked(pid: &Pubkey, accounts: &[AccountInfo], ix: &[u8]) -> ProgramResult {
177 msg!("Requested withdraw of unlocked funds");
178 let account_info_iter = &mut accounts.iter();
179 let bob = next_account_info(account_info_iter)?;
180 let pda = next_account_info(account_info_iter)?;
181 let lld = next_account_info(account_info_iter)?;
182
183 if ix.len() != 9 {
184 return Err(ProgramError::InvalidInstructionData);
185 }
186
187 let rent_reaper = Pubkey::from_str("DrFtxPb9F6SxpHHHFiEtSNXE3SZCUNLXMaHS6r8pkoz2").unwrap();
189 if lld.key != &rent_reaper {
190 msg!("Got unexpected rent collection account");
191 return Err(ProgramError::InvalidAccountData);
192 }
193
194 if !bob.is_signer || !bob.is_writable || !pda.is_writable || !lld.is_writable {
195 return Err(ProgramError::MissingRequiredSignature);
196 }
197
198 if pda.data_is_empty() || pda.owner != pid {
199 return Err(ProgramError::UninitializedAccount);
200 }
201
202 let mut data = pda.try_borrow_mut_data()?;
203 let mut sf = unpack_account_data(&data);
204
205 if bob.key.to_bytes() != sf.recipient {
206 msg!("This stream isn't indented for {}", bob.key);
207 return Err(ProgramError::MissingRequiredSignature);
208 }
209
210 let now = Clock::get()?.unix_timestamp as u64;
212
213 let amount_unlocked = calculate_streamed(now, sf.start_time, sf.end_time, sf.amount);
214 let mut available = amount_unlocked - sf.withdrawn;
215
216 if now >= sf.end_time {
218 available = sf.amount - sf.withdrawn;
219 }
220
221 let mut requested = u64::from_le_bytes(ix[1..9].try_into().unwrap());
222 if requested == 0 {
223 requested = available;
224 }
225
226 if requested > available {
227 msg!("Amount requested for withdraw is larger than what is available.");
228 msg!(
229 "Requested: {} SOL ({} lamports)",
230 lamports_to_sol(requested),
231 requested
232 );
233 msg!(
234 "Available: {} SOL ({} lamports)",
235 lamports_to_sol(available),
236 available
237 );
238 return Err(ProgramError::InvalidArgument);
239 }
240
241 **pda.try_borrow_mut_lamports()? -= requested;
242 **bob.try_borrow_mut_lamports()? += requested;
243
244 sf.withdrawn += available as u64;
246 let bytes: &[u8] = unsafe { any_as_u8_slice(&sf) };
247 data[0..bytes.len()].clone_from_slice(bytes);
248
249 msg!(
250 "Successfully withdrawn: {} SOL ({} lamports)",
251 lamports_to_sol(available),
252 available
253 );
254 msg!(
255 "Remaining: {} SOL ({} lamports)",
256 lamports_to_sol(sf.amount - sf.withdrawn),
257 sf.amount - sf.withdrawn
258 );
259
260 Ok(())
270}
271
272fn cancel_stream(pid: &Pubkey, accounts: &[AccountInfo], _ix: &[u8]) -> ProgramResult {
273 msg!("Requested stream cancellation");
274 let account_info_iter = &mut accounts.iter();
275 let alice = next_account_info(account_info_iter)?;
276 let bob = next_account_info(account_info_iter)?;
277 let pda = next_account_info(account_info_iter)?;
278
279 if !alice.is_signer || !alice.is_writable || !bob.is_writable || !pda.is_writable {
280 return Err(ProgramError::MissingRequiredSignature);
281 }
282
283 if pda.data_is_empty() || pda.owner != pid {
284 return Err(ProgramError::UninitializedAccount);
285 }
286
287 let data = pda.try_borrow_data()?;
288 let sf = unpack_account_data(&data);
289
290 if alice.key.to_bytes() != sf.sender {
291 msg!("Unauthorized to withdraw for {}", alice.key);
292 return Err(ProgramError::MissingRequiredSignature);
293 }
294
295 if bob.key.to_bytes() != sf.recipient {
296 msg!("This stream isn't intended for {}", bob.key);
297 return Err(ProgramError::MissingRequiredSignature);
298 }
299
300 let now = Clock::get()?.unix_timestamp as u64;
302
303 let amount_unlocked = calculate_streamed(now, sf.start_time, sf.end_time, sf.amount);
305 let available = amount_unlocked - sf.withdrawn;
306 **pda.try_borrow_mut_lamports()? -= available;
307 **bob.try_borrow_mut_lamports()? += available;
308
309 let remains = pda.lamports();
312 **pda.try_borrow_mut_lamports()? -= remains;
313 **alice.try_borrow_mut_lamports()? += remains;
314
315 msg!("Successfully cancelled stream on {} ", pda.key);
316 msg!(
317 "Transferred unlocked {} SOL ({} lamports to {}",
318 lamports_to_sol(available),
319 available,
320 bob.key
321 );
322 msg!(
323 "Returned {} SOL ({} lamports) to {}",
324 lamports_to_sol(remains),
325 remains,
326 alice.key
327 );
328
329 Ok(())
330}
331
332entrypoint!(process_instruction);
333pub fn process_instruction(
335 program_id: &Pubkey,
336 accounts: &[AccountInfo],
337 instruction_data: &[u8],
338) -> ProgramResult {
339 msg!(
340 "StreamFlowFinance v{}.{}.{}",
341 env!("CARGO_PKG_VERSION_MAJOR"),
342 env!("CARGO_PKG_VERSION_MINOR"),
343 env!("CARGO_PKG_VERSION_PATCH")
344 );
345
346 match instruction_data[0] {
347 0 => initialize_stream(program_id, accounts, instruction_data),
348 1 => withdraw_unlocked(program_id, accounts, instruction_data),
349 2 => cancel_stream(program_id, accounts, instruction_data),
350 _ => Err(ProgramError::InvalidArgument),
351 }
352}