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}