streamflow_timelock/
state.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::{account_info::AccountInfo, pubkey::Pubkey};
18
19/// The struct containing instructions for initializing a stream
20#[repr(C)]
21#[derive(BorshDeserialize, BorshSerialize, Clone, Debug)]
22pub struct StreamInstruction {
23    /// Timestamp when the tokens start vesting
24    pub start_time: u64,
25    /// Timestamp when all tokens are fully vested
26    pub end_time: u64,
27    /// Initially deposited amount of tokens (currently not used, set to `total_amount` and left for extension in future)
28    pub deposited_amount: u64,
29    /// Total amount of the tokens in the escrow account if contract is fully vested
30    pub total_amount: u64,
31    /// Time step (period) in seconds per which the vesting occurs
32    pub period: u64,
33    /// Vesting contract "cliff" timestamp
34    pub cliff: u64,
35    /// Amount unlocked at the "cliff" timestamp
36    pub cliff_amount: u64,
37    /// Whether or not a stream can be canceled by a sender (currently not used, set to TRUE)
38    pub is_cancelable_by_sender: bool,
39    /// Whether or not a stream can be canceled by a recipient (currently not used, set to FALSE)
40    pub is_cancelable_by_recipient: bool,
41    /// Whether or not a 3rd party can initiate withdraw in the name of recipient (currently not used, set to FALSE)
42    pub is_withdrawal_public: bool,
43    /// Whether or not a recipient can transfer the stream (currently not used, set to TRUE)
44    pub is_transferable: bool,
45    //4 bytes of padding to make the struct size multiple of 64 bits (8 bytes), non-meaningful data.
46    pub padding: u32,
47}
48
49impl Default for StreamInstruction {
50    fn default() -> Self {
51        StreamInstruction {
52            start_time: 0,
53            end_time: 0,
54            deposited_amount: 0,
55            total_amount: 0,
56            period: 1,
57            cliff: 0,
58            cliff_amount: 0,
59            is_cancelable_by_sender: true,
60            is_cancelable_by_recipient: false,
61            is_withdrawal_public: false,
62            is_transferable: true,
63            padding: 0,
64        }
65    }
66}
67
68impl StreamInstruction {
69    pub fn new(
70        start_time: u64,
71        end_time: u64,
72        total_amount: u64,
73        period: u64,
74        cliff: u64,
75        cliff_amount: u64,
76    ) -> Self {
77        Self {
78            start_time,
79            end_time,
80            total_amount,
81            deposited_amount: total_amount,
82            period,
83            cliff,
84            cliff_amount,
85            is_cancelable_by_sender: true,
86            is_cancelable_by_recipient: false,
87            is_withdrawal_public: false,
88            is_transferable: true,
89            padding: 0,
90        }
91    }
92}
93
94/// TokenStreamData is the struct containing metadata for an SPL token stream.
95#[derive(BorshSerialize, BorshDeserialize, Default, Debug)]
96#[repr(C)]
97pub struct TokenStreamData {
98    /// Magic bytes
99    pub magic: u64,
100    /// Timestamp when stream was created
101    pub created_at: u64,
102    /// Amount of funds withdrawn
103    pub withdrawn_amount: u64,
104    /// Timestamp when stream was canceled (if canceled)
105    pub canceled_at: u64,
106    /// Timestamp at which stream can be safely canceled by a 3rd party
107    /// (Stream is either fully vested or there isn't enough capital to
108    /// keep it active)
109    pub cancellable_at: u64,
110    /// Timestamp of the last withdrawal
111    pub last_withdrawn_at: u64,
112    /// Pubkey of the stream initializer
113    pub sender: Pubkey,
114    /// Pubkey of the stream initializer's token account
115    pub sender_tokens: Pubkey,
116    /// Pubkey of the stream recipient
117    pub recipient: Pubkey,
118    /// Pubkey of the stream recipient's token account
119    pub recipient_tokens: Pubkey,
120    /// Pubkey of the token mint
121    pub mint: Pubkey,
122    /// Pubkey of the account holding the locked tokens
123    pub escrow_tokens: Pubkey,
124    /// The stream instruction
125    pub ix: StreamInstruction,
126}
127
128#[allow(clippy::too_many_arguments)]
129impl TokenStreamData {
130    /// Initialize a new `TokenStreamData` struct.
131    pub fn new(
132        created_at: u64,
133        sender: Pubkey,
134        sender_tokens: Pubkey,
135        recipient: Pubkey,
136        recipient_tokens: Pubkey,
137        mint: Pubkey,
138        escrow_tokens: Pubkey,
139        start_time: u64,
140        end_time: u64,
141        total_amount: u64,
142        period: u64,
143        cliff: u64,
144        cliff_amount: u64,
145    ) -> Self {
146        let ix = StreamInstruction {
147            start_time,
148            end_time,
149            deposited_amount: total_amount,
150            total_amount,
151            period,
152            cliff,
153            cliff_amount,
154            is_cancelable_by_sender: true,
155            is_cancelable_by_recipient: false,
156            is_withdrawal_public: false,
157            is_transferable: true,
158            padding: 0,
159        };
160
161        Self {
162            magic: 0,
163            created_at,
164            withdrawn_amount: 0,
165            canceled_at: 0,
166            cancellable_at: end_time,
167            last_withdrawn_at: 0,
168            sender,
169            sender_tokens,
170            recipient,
171            recipient_tokens,
172            mint,
173            escrow_tokens,
174            ix,
175        }
176    }
177
178    /// Calculate amount available for withdrawal with given timestamp.
179    pub fn available(&self, now: u64) -> u64 {
180        if self.ix.start_time > now || self.ix.cliff > now {
181            return 0;
182        }
183
184        if now >= self.ix.end_time {
185            return self.ix.total_amount - self.withdrawn_amount;
186        }
187
188        let cliff = if self.ix.cliff > 0 {
189            self.ix.cliff
190        } else {
191            self.ix.start_time
192        };
193
194        let cliff_amount = if self.ix.cliff_amount > 0 {
195            self.ix.cliff_amount
196        } else {
197            0
198        };
199
200        let num_periods = (self.ix.end_time - cliff) as f64 / self.ix.period as f64;
201        let period_amount = (self.ix.total_amount - cliff_amount) as f64 / num_periods;
202        let periods_passed = (now - cliff) / self.ix.period;
203        (periods_passed as f64 * period_amount) as u64 + cliff_amount - self.withdrawn_amount
204    }
205}
206
207/// The account-holding struct for the stream initialization instruction
208#[derive(Debug)]
209pub struct InitializeAccounts<'a> {
210    /// The main wallet address of the initializer.
211    pub sender: AccountInfo<'a>,
212    /// The associated token account address of `sender`.
213    pub sender_tokens: AccountInfo<'a>,
214    /// The main wallet address of the recipient.
215    pub recipient: AccountInfo<'a>,
216    /// The associated token account address of `recipient`.
217    /// (Can be either empty or initialized).
218    pub recipient_tokens: AccountInfo<'a>,
219    /// The account holding the stream metadata.
220    /// Expects empty (non-initialized) account.
221    pub metadata: AccountInfo<'a>,
222    /// The escrow account holding the stream funds.
223    /// Expects empty (non-initialized) account.
224    pub escrow_tokens: AccountInfo<'a>,
225    /// The SPL token mint account
226    pub mint: AccountInfo<'a>,
227    /// The Rent Sysvar account
228    pub rent: AccountInfo<'a>,
229    /// The SPL program needed in case an associated account
230    /// for the new recipient is being created.
231    pub token_program: AccountInfo<'a>,
232    /// The Associated Token program needed in case associated
233    /// account for the new recipient is being created.
234    pub associated_token_program: AccountInfo<'a>,
235    /// The Solana system program
236    pub system_program: AccountInfo<'a>,
237}
238
239/// The account-holding struct for the stream withdraw instruction
240pub struct WithdrawAccounts<'a> {
241    /// Account invoking transaction. Must match `recipient`
242    // Same as `recipient` if `is_withdrawal_public == true`, can be any other account otherwise.
243    pub withdraw_authority: AccountInfo<'a>,
244    /// Sender account is needed to collect the rent for escrow token account after the last withdrawal
245    pub sender: AccountInfo<'a>,
246    /// Recipient's wallet address
247    pub recipient: AccountInfo<'a>,
248    /// The associated token account address of a stream `recipient`
249    pub recipient_tokens: AccountInfo<'a>,
250    /// The account holding the stream metadata
251    pub metadata: AccountInfo<'a>,
252    /// The escrow account holding the stream funds
253    pub escrow_tokens: AccountInfo<'a>,
254    /// The SPL token mint account
255    pub mint: AccountInfo<'a>,
256    /// The SPL token program
257    pub token_program: AccountInfo<'a>,
258}
259
260/// The account-holding struct for the stream cancel instruction
261pub struct CancelAccounts<'a> {
262    /// Account invoking cancel. Must match `sender`.
263    //Can be either `sender` or `recipient` depending on the value of `is_cancelable_by_sender` and `is_cancelable_by_recipient`, respectively
264    pub cancel_authority: AccountInfo<'a>,
265    /// The main wallet address of the initializer
266    pub sender: AccountInfo<'a>,
267    /// The associated token account address of `sender`
268    pub sender_tokens: AccountInfo<'a>,
269    /// The main wallet address of the recipient
270    pub recipient: AccountInfo<'a>,
271    /// The associated token account address of `recipient`
272    pub recipient_tokens: AccountInfo<'a>,
273    /// The account holding the stream metadata
274    pub metadata: AccountInfo<'a>,
275    /// The escrow account holding the stream funds
276    pub escrow_tokens: AccountInfo<'a>,
277    /// The SPL token mint account
278    pub mint: AccountInfo<'a>,
279    /// The SPL token program
280    pub token_program: AccountInfo<'a>,
281}
282
283/// Accounts needed for updating stream recipient
284pub struct TransferAccounts<'a> {
285    /// Wallet address of the existing recipient
286    pub existing_recipient: AccountInfo<'a>,
287    /// New stream beneficiary
288    pub new_recipient: AccountInfo<'a>,
289    /// New stream beneficiary's token account.
290    /// If not initialized, it will be created and
291    /// `existing_recipient` is the fee payer
292    pub new_recipient_tokens: AccountInfo<'a>,
293    /// The account holding the stream metadata
294    pub metadata: AccountInfo<'a>,
295    /// The escrow account holding the stream funds
296    pub escrow_tokens: AccountInfo<'a>,
297    /// The SPL token mint account
298    pub mint: AccountInfo<'a>,
299    /// Rent account
300    pub rent: AccountInfo<'a>,
301    /// The SPL program needed in case associated account
302    /// for the new recipients is being created.
303    pub token_program: AccountInfo<'a>,
304    /// The Associated Token program needed in case associated
305    /// account for the new recipients is being created.
306    pub associated_token_program: AccountInfo<'a>,
307    /// The Solana system program needed in case associated
308    /// account for the new recipients is being created.
309    pub system_program: AccountInfo<'a>,
310}