streamflow_timelock/
token.rs

1// Copyright (c) 2021 Streamflow Labs Limited <legal@streamflowlabs.com>
2//
3// This file is part of streamflow-finance/timelock-crate
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License version 3
7// as published by the Free Software Foundation.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16use borsh::{BorshDeserialize, BorshSerialize};
17use solana_program::{
18    entrypoint::ProgramResult,
19    msg,
20    program::{invoke, invoke_signed},
21    program_error::ProgramError,
22    program_pack::Pack,
23    pubkey::Pubkey,
24    system_instruction, system_program, sysvar,
25    sysvar::{clock::Clock, fees::Fees, rent::Rent, Sysvar},
26};
27use spl_associated_token_account::{create_associated_token_account, get_associated_token_address};
28
29use crate::state::{
30    CancelAccounts, InitializeAccounts, StreamInstruction, TokenStreamData, TransferAccounts,
31    WithdrawAccounts,
32};
33use crate::utils::{
34    duration_sanity, encode_base10, pretty_time, unpack_mint_account, unpack_token_account,
35};
36
37/// Initialize an SPL token stream
38///
39/// The function shall initialize new accounts to hold the tokens,
40/// and the stream's metadata. Both accounts will be funded to be
41/// rent-exempt if necessary. When the stream is finished, these
42/// shall be returned to the stream initializer.
43pub fn create(
44    program_id: &Pubkey,
45    acc: InitializeAccounts,
46    ix: StreamInstruction,
47) -> ProgramResult {
48    msg!("Initializing SPL token stream");
49
50    if !acc.escrow_tokens.data_is_empty() || !acc.metadata.data_is_empty() {
51        return Err(ProgramError::AccountAlreadyInitialized);
52    }
53
54    if !acc.sender.is_writable
55        || !acc.sender_tokens.is_writable
56        || !acc.recipient.is_writable
57        || !acc.recipient_tokens.is_writable
58        || !acc.metadata.is_writable
59        || !acc.escrow_tokens.is_writable
60    {
61        return Err(ProgramError::InvalidAccountData);
62    }
63
64    let (escrow_tokens_pubkey, nonce) =
65        Pubkey::find_program_address(&[acc.metadata.key.as_ref()], program_id);
66    let recipient_tokens_key = get_associated_token_address(acc.recipient.key, acc.mint.key);
67
68    if acc.system_program.key != &system_program::id()
69        || acc.token_program.key != &spl_token::id()
70        || acc.rent.key != &sysvar::rent::id()
71        || acc.escrow_tokens.key != &escrow_tokens_pubkey
72        || acc.recipient_tokens.key != &recipient_tokens_key
73    {
74        return Err(ProgramError::InvalidAccountData);
75    }
76
77    if !acc.sender.is_signer || !acc.metadata.is_signer {
78        return Err(ProgramError::MissingRequiredSignature);
79    }
80
81    let sender_token_info = unpack_token_account(&acc.sender_tokens)?;
82    let mint_info = unpack_mint_account(&acc.mint)?;
83
84    if &sender_token_info.mint != acc.mint.key {
85        // Mint mismatch
86        return Err(ProgramError::Custom(3));
87    }
88
89    let now = Clock::get()?.unix_timestamp as u64;
90    if !duration_sanity(now, ix.start_time, ix.end_time, ix.cliff) {
91        msg!("Error: Given timestamps are invalid");
92        return Err(ProgramError::InvalidArgument);
93    }
94
95    // We also transfer enough to be rent-exempt on the metadata account.
96    let metadata_struct_size = std::mem::size_of::<TokenStreamData>();
97    let tokens_struct_size = spl_token::state::Account::LEN;
98    let cluster_rent = Rent::get()?;
99    let metadata_rent = cluster_rent.minimum_balance(metadata_struct_size);
100    let mut tokens_rent = cluster_rent.minimum_balance(tokens_struct_size);
101    if acc.recipient_tokens.data_is_empty() {
102        tokens_rent += cluster_rent.minimum_balance(tokens_struct_size);
103    }
104
105    let fees = Fees::get()?;
106    let lps = fees.fee_calculator.lamports_per_signature;
107
108    if acc.sender.lamports() < metadata_rent + tokens_rent + (2 * lps) {
109        msg!("Error: Insufficient funds in {}", acc.sender.key);
110        return Err(ProgramError::InsufficientFunds);
111    }
112
113    if sender_token_info.amount < ix.total_amount {
114        msg!("Error: Insufficient tokens in sender's wallet");
115        return Err(ProgramError::InsufficientFunds);
116    }
117
118    let metadata = TokenStreamData::new(
119        now,
120        *acc.sender.key,
121        *acc.sender_tokens.key,
122        *acc.recipient.key,
123        *acc.recipient_tokens.key,
124        *acc.mint.key,
125        *acc.escrow_tokens.key,
126        ix.start_time,
127        ix.end_time,
128        ix.total_amount,
129        ix.period,
130        ix.cliff,
131        ix.cliff_amount,
132    );
133    let bytes = metadata.try_to_vec()?;
134
135    if acc.recipient_tokens.data_is_empty() {
136        msg!("Initializing recipient's associated token account");
137        invoke(
138            &create_associated_token_account(acc.sender.key, acc.recipient.key, acc.mint.key),
139            &[
140                acc.sender.clone(),
141                acc.recipient_tokens.clone(),
142                acc.recipient.clone(),
143                acc.mint.clone(),
144                acc.system_program.clone(),
145                acc.token_program.clone(),
146                acc.rent.clone(),
147            ],
148        )?;
149    }
150
151    msg!("Creating account for holding metadata");
152    invoke(
153        &system_instruction::create_account(
154            acc.sender.key,
155            acc.metadata.key,
156            metadata_rent,
157            metadata_struct_size as u64,
158            program_id,
159        ),
160        &[
161            acc.sender.clone(),
162            acc.metadata.clone(),
163            acc.system_program.clone(),
164        ],
165    )?;
166
167    // Write the metadata to the account
168    let mut data = acc.metadata.try_borrow_mut_data()?;
169    data[0..bytes.len()].clone_from_slice(&bytes);
170
171    let seeds = [acc.metadata.key.as_ref(), &[nonce]];
172    msg!("Creating account for holding tokens");
173    invoke_signed(
174        &system_instruction::create_account(
175            acc.sender.key,
176            acc.escrow_tokens.key,
177            cluster_rent.minimum_balance(tokens_struct_size),
178            tokens_struct_size as u64,
179            &spl_token::id(),
180        ),
181        &[
182            acc.sender.clone(),
183            acc.escrow_tokens.clone(),
184            acc.system_program.clone(),
185        ],
186        &[&seeds],
187    )?;
188
189    msg!("Initializing escrow account for {} token", acc.mint.key);
190    invoke(
191        &spl_token::instruction::initialize_account(
192            acc.token_program.key,
193            acc.escrow_tokens.key,
194            acc.mint.key,
195            acc.escrow_tokens.key,
196        )?,
197        &[
198            acc.token_program.clone(),
199            acc.escrow_tokens.clone(),
200            acc.mint.clone(),
201            acc.escrow_tokens.clone(),
202            acc.rent.clone(),
203        ],
204    )?;
205
206    msg!("Moving funds into escrow account");
207    invoke(
208        &spl_token::instruction::transfer(
209            acc.token_program.key,
210            acc.sender_tokens.key,
211            acc.escrow_tokens.key,
212            acc.sender.key,
213            &[],
214            metadata.ix.total_amount,
215        )?,
216        &[
217            acc.sender_tokens.clone(),
218            acc.escrow_tokens.clone(),
219            acc.sender.clone(),
220            acc.token_program.clone(),
221        ],
222    )?;
223
224    msg!(
225        "Successfully initialized {} {} token stream for {}",
226        encode_base10(metadata.ix.total_amount, mint_info.decimals.into()),
227        metadata.mint,
228        acc.recipient.key
229    );
230    msg!("Called by {}", acc.sender.key);
231    msg!("Metadata written in {}", acc.metadata.key);
232    msg!("Funds locked in {}", acc.escrow_tokens.key);
233    msg!(
234        "Stream duration is {}",
235        pretty_time(metadata.ix.end_time - metadata.ix.start_time)
236    );
237
238    if metadata.ix.cliff > 0 && metadata.ix.cliff_amount > 0 {
239        msg!("Cliff happens at {}", pretty_time(metadata.ix.cliff));
240    }
241
242    Ok(())
243}
244
245/// Withdraw from an SPL Token stream
246///
247/// The function will read the instructions from the metadata account and see
248/// if there are any unlocked funds. If so, they will be transferred from the
249/// escrow account to the stream recipient. If the entire amount has been
250/// withdrawn, the remaining rents shall be returned to the stream initializer.
251pub fn withdraw(program_id: &Pubkey, acc: WithdrawAccounts, amount: u64) -> ProgramResult {
252    msg!("Withdrawing from SPL token stream");
253
254    if acc.escrow_tokens.data_is_empty()
255        || acc.escrow_tokens.owner != &spl_token::id()
256        || acc.metadata.data_is_empty()
257        || acc.metadata.owner != program_id
258    {
259        return Err(ProgramError::UninitializedAccount);
260    }
261
262    if !acc.recipient.is_writable
263        || !acc.recipient_tokens.is_writable
264        || !acc.metadata.is_writable
265        || !acc.escrow_tokens.is_writable
266    {
267        return Err(ProgramError::InvalidAccountData);
268    }
269
270    let (escrow_tokens_pubkey, nonce) =
271        Pubkey::find_program_address(&[acc.metadata.key.as_ref()], program_id);
272    let recipient_tokens_key = get_associated_token_address(acc.recipient.key, acc.mint.key);
273
274    if acc.token_program.key != &spl_token::id()
275        || acc.escrow_tokens.key != &escrow_tokens_pubkey
276        || acc.recipient_tokens.key != &recipient_tokens_key
277        || acc.withdraw_authority.key != acc.recipient.key
278    {
279        return Err(ProgramError::InvalidAccountData);
280    }
281
282    if !acc.withdraw_authority.is_signer {
283        return Err(ProgramError::MissingRequiredSignature);
284    }
285
286    let mut data = acc.metadata.try_borrow_mut_data()?;
287    let mut metadata = match TokenStreamData::try_from_slice(&data) {
288        Ok(v) => v,
289        Err(_) => return Err(ProgramError::InvalidAccountData),
290    };
291
292    let mint_info = unpack_mint_account(&acc.mint)?;
293
294    if acc.recipient.key != &metadata.recipient
295        || acc.recipient_tokens.key != &metadata.recipient_tokens
296        || acc.mint.key != &metadata.mint
297        || acc.escrow_tokens.key != &metadata.escrow_tokens
298    {
299        msg!("Error: Metadata does not match given accounts");
300        return Err(ProgramError::InvalidAccountData);
301    }
302
303    let now = Clock::get()?.unix_timestamp as u64;
304    let available = metadata.available(now);
305    let requested: u64;
306
307    if amount > available {
308        msg!("Amount requested for withdraw is more than what is available");
309        return Err(ProgramError::InvalidArgument);
310    }
311
312    // 0 == MAX
313    if amount == 0 {
314        requested = available;
315    } else {
316        requested = amount;
317    }
318
319    let seeds = [acc.metadata.key.as_ref(), &[nonce]];
320    invoke_signed(
321        &spl_token::instruction::transfer(
322            acc.token_program.key,
323            acc.escrow_tokens.key,
324            acc.recipient_tokens.key,
325            acc.escrow_tokens.key,
326            &[],
327            requested,
328        )?,
329        &[
330            acc.escrow_tokens.clone(),    // src
331            acc.recipient_tokens.clone(), // dest
332            acc.escrow_tokens.clone(),    // auth
333            acc.token_program.clone(),    // program
334        ],
335        &[&seeds],
336    )?;
337
338    metadata.withdrawn_amount += requested;
339    metadata.last_withdrawn_at = now;
340    let bytes = metadata.try_to_vec()?;
341    data[0..bytes.len()].clone_from_slice(&bytes);
342
343    // Return rent when everything is withdrawn
344    if metadata.withdrawn_amount == metadata.ix.total_amount {
345        if !acc.sender.is_writable || acc.sender.key != &metadata.sender {
346            return Err(ProgramError::InvalidAccountData);
347        }
348        //TODO: Close metadata account once there is alternative storage solution for historic data.
349        // let rent = acc.metadata.lamports();
350        // **acc.metadata.try_borrow_mut_lamports()? -= rent;
351        // **acc.sender.try_borrow_mut_lamports()? += rent;
352
353        let escrow_tokens_rent = acc.escrow_tokens.lamports();
354        //Close escrow token account
355        msg!(
356            "Returning {} lamports (rent) to {}",
357            escrow_tokens_rent,
358            acc.sender.key
359        );
360        invoke_signed(
361            &spl_token::instruction::close_account(
362                acc.token_program.key,
363                acc.escrow_tokens.key,
364                acc.sender.key,
365                acc.escrow_tokens.key,
366                &[],
367            )?,
368            &[
369                acc.escrow_tokens.clone(),
370                acc.sender.clone(),
371                acc.escrow_tokens.clone(),
372            ],
373            &[&seeds],
374        )?;
375    }
376
377    msg!(
378        "Withdrawn: {} {} tokens",
379        encode_base10(requested, mint_info.decimals.into()),
380        metadata.mint
381    );
382    msg!(
383        "Remaining: {} {} tokens",
384        encode_base10(
385            metadata.ix.total_amount - metadata.withdrawn_amount,
386            mint_info.decimals.into()
387        ),
388        metadata.mint
389    );
390
391    Ok(())
392}
393
394/// Cancel an SPL Token stream
395///
396/// The function will read the instructions from the metadata account and see
397/// if there are any unlocked funds. If so, they will be transferred to the
398/// stream recipient.
399pub fn cancel(program_id: &Pubkey, acc: CancelAccounts) -> ProgramResult {
400    msg!("Cancelling SPL token stream");
401
402    if acc.escrow_tokens.data_is_empty()
403        || acc.escrow_tokens.owner != &spl_token::id()
404        || acc.metadata.data_is_empty()
405        || acc.metadata.owner != program_id
406    {
407        return Err(ProgramError::UninitializedAccount);
408    }
409
410    if !acc.sender.is_writable
411        || !acc.sender_tokens.is_writable
412        || !acc.recipient.is_writable
413        || !acc.recipient_tokens.is_writable
414        || !acc.metadata.is_writable
415        || !acc.escrow_tokens.is_writable
416    {
417        return Err(ProgramError::InvalidAccountData);
418    }
419
420    let (escrow_tokens_pubkey, nonce) =
421        Pubkey::find_program_address(&[acc.metadata.key.as_ref()], program_id);
422    let recipient_tokens_key = get_associated_token_address(acc.recipient.key, acc.mint.key);
423
424    if acc.token_program.key != &spl_token::id()
425        || acc.escrow_tokens.key != &escrow_tokens_pubkey
426        || acc.recipient_tokens.key != &recipient_tokens_key
427        || acc.cancel_authority.key != acc.sender.key
428    {
429        return Err(ProgramError::InvalidAccountData);
430    }
431
432    if !acc.cancel_authority.is_signer {
433        return Err(ProgramError::MissingRequiredSignature);
434    }
435
436    let mut data = acc.metadata.try_borrow_mut_data()?;
437    let mut metadata = match TokenStreamData::try_from_slice(&data) {
438        Ok(v) => v,
439        Err(_) => return Err(ProgramError::InvalidAccountData),
440    };
441
442    let mint_info = unpack_mint_account(&acc.mint)?;
443
444    if acc.sender.key != &metadata.sender
445        || acc.sender_tokens.key != &metadata.sender_tokens
446        || acc.recipient.key != &metadata.recipient
447        || acc.recipient_tokens.key != &metadata.recipient_tokens
448        || acc.mint.key != &metadata.mint
449        || acc.escrow_tokens.key != &metadata.escrow_tokens
450    {
451        return Err(ProgramError::InvalidAccountData);
452    }
453
454    let now = Clock::get()?.unix_timestamp as u64;
455    let available = metadata.available(now);
456
457    let seeds = [acc.metadata.key.as_ref(), &[nonce]];
458    invoke_signed(
459        &spl_token::instruction::transfer(
460            acc.token_program.key,
461            acc.escrow_tokens.key,
462            acc.recipient_tokens.key,
463            acc.escrow_tokens.key,
464            &[],
465            available,
466        )?,
467        &[
468            acc.escrow_tokens.clone(),    // src
469            acc.recipient_tokens.clone(), // dest
470            acc.escrow_tokens.clone(),    // auth
471            acc.token_program.clone(),    // program
472        ],
473        &[&seeds],
474    )?;
475
476    metadata.withdrawn_amount += available;
477    let remains = metadata.ix.total_amount - metadata.withdrawn_amount;
478
479    // Return any remaining funds to the stream initializer
480    if remains > 0 {
481        invoke_signed(
482            &spl_token::instruction::transfer(
483                acc.token_program.key,
484                acc.escrow_tokens.key,
485                acc.sender_tokens.key,
486                acc.escrow_tokens.key,
487                &[],
488                remains,
489            )?,
490            &[
491                acc.escrow_tokens.clone(),
492                acc.sender_tokens.clone(),
493                acc.escrow_tokens.clone(),
494                acc.token_program.clone(),
495            ],
496            &[&seeds],
497        )?;
498    }
499
500    let rent_escrow_tokens = acc.escrow_tokens.lamports();
501    // let remains_meta = acc.metadata.lamports();
502
503    //Close escrow token account
504    invoke_signed(
505        &spl_token::instruction::close_account(
506            acc.token_program.key,
507            acc.escrow_tokens.key,
508            acc.sender.key,
509            acc.escrow_tokens.key,
510            &[],
511        )?,
512        &[
513            acc.escrow_tokens.clone(),
514            acc.sender.clone(),
515            acc.escrow_tokens.clone(),
516        ],
517        &[&seeds],
518    )?;
519
520    metadata.last_withdrawn_at = now;
521    metadata.canceled_at = now;
522    // Write the metadata to the account
523    let bytes = metadata.try_to_vec().unwrap();
524    data[0..bytes.len()].clone_from_slice(&bytes);
525
526    msg!(
527        "Transferred: {} {} tokens",
528        encode_base10(available, mint_info.decimals.into()),
529        metadata.mint
530    );
531    msg!(
532        "Returned: {} {} tokens",
533        encode_base10(remains, mint_info.decimals.into()),
534        metadata.mint
535    );
536    msg!(
537        "Returned rent: {} lamports",
538        rent_escrow_tokens /* + remains_meta */
539    );
540
541    Ok(())
542}
543
544pub fn transfer_recipient(program_id: &Pubkey, acc: TransferAccounts) -> ProgramResult {
545    msg!("Transferring stream recipient");
546    if acc.metadata.data_is_empty()
547        || acc.metadata.owner != program_id
548        || acc.escrow_tokens.data_is_empty()
549        || acc.escrow_tokens.owner != &spl_token::id()
550    {
551        return Err(ProgramError::UninitializedAccount);
552    }
553
554    if !acc.existing_recipient.is_signer {
555        return Err(ProgramError::MissingRequiredSignature);
556    }
557
558    if !acc.metadata.is_writable
559        || !acc.existing_recipient.is_writable
560        || !acc.new_recipient_tokens.is_writable
561    {
562        return Err(ProgramError::InvalidAccountData);
563    }
564
565    let mut data = acc.metadata.try_borrow_mut_data()?;
566    let mut metadata = match TokenStreamData::try_from_slice(&data) {
567        Ok(v) => v,
568        Err(_) => return Err(ProgramError::InvalidAccountData),
569    };
570
571    let (escrow_tokens_pubkey, _) =
572        Pubkey::find_program_address(&[acc.metadata.key.as_ref()], program_id);
573    let new_recipient_tokens_key =
574        get_associated_token_address(acc.new_recipient.key, acc.mint.key);
575
576    if acc.new_recipient_tokens.key != &new_recipient_tokens_key
577        || acc.mint.key != &metadata.mint
578        || acc.existing_recipient.key != &metadata.recipient
579        || acc.escrow_tokens.key != &metadata.escrow_tokens
580        || acc.escrow_tokens.key != &escrow_tokens_pubkey
581        || acc.token_program.key != &spl_token::id()
582        || acc.system_program.key != &system_program::id()
583        || acc.rent.key != &sysvar::rent::id()
584    {
585        return Err(ProgramError::InvalidAccountData);
586    }
587
588    if acc.new_recipient_tokens.data_is_empty() {
589        // Initialize a new_beneficiary_owner account
590        let tokens_struct_size = spl_token::state::Account::LEN;
591        let cluster_rent = Rent::get()?;
592        let tokens_rent = cluster_rent.minimum_balance(tokens_struct_size);
593        let fees = Fees::get()?;
594        let lps = fees.fee_calculator.lamports_per_signature;
595
596        if acc.existing_recipient.lamports() < tokens_rent + lps {
597            msg!(
598                "Error: Insufficient funds in {}",
599                acc.existing_recipient.key
600            );
601            return Err(ProgramError::InsufficientFunds);
602        }
603
604        msg!("Initializing new recipient's associated token account");
605        invoke(
606            &create_associated_token_account(
607                acc.existing_recipient.key,
608                acc.new_recipient.key,
609                acc.mint.key,
610            ),
611            &[
612                acc.existing_recipient.clone(),   // Funding
613                acc.new_recipient_tokens.clone(), // Associated token account's address
614                acc.new_recipient.clone(),        // Wallet address
615                acc.mint.clone(),
616                acc.system_program.clone(),
617                acc.token_program.clone(),
618                acc.rent.clone(),
619            ],
620        )?;
621    }
622
623    // Update recipient
624    metadata.recipient = *acc.new_recipient.key;
625    metadata.recipient_tokens = *acc.new_recipient_tokens.key;
626
627    let bytes = metadata.try_to_vec()?;
628    data[0..bytes.len()].clone_from_slice(&bytes);
629
630    Ok(())
631}