spl_single_pool/
instruction.rs

1//! Instruction types
2
3#![allow(clippy::too_many_arguments)]
4
5use {
6    crate::{
7        find_default_deposit_account_address_and_seed, find_pool_address, find_pool_mint_address,
8        find_pool_mint_authority_address, find_pool_mpl_authority_address, find_pool_stake_address,
9        find_pool_stake_authority_address,
10        inline_mpl_token_metadata::{self, pda::find_metadata_account},
11        state::SinglePool,
12    },
13    borsh::{BorshDeserialize, BorshSerialize},
14    solana_program::{
15        instruction::{AccountMeta, Instruction},
16        program_pack::Pack,
17        pubkey::Pubkey,
18        rent::Rent,
19        stake, system_instruction, system_program, sysvar,
20    },
21};
22
23/// Instructions supported by the `SinglePool` program.
24#[repr(C)]
25#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
26pub enum SinglePoolInstruction {
27    ///   Initialize the mint and stake account for a new single-validator
28    ///   stake pool. The pool stake account must contain the rent-exempt
29    ///   minimum plus the minimum delegation. No tokens will be minted: to
30    ///   deposit more, use `Deposit` after `InitializeStake`.
31    ///
32    ///   0. `[]` Validator vote account
33    ///   1. `[w]` Pool account
34    ///   2. `[w]` Pool stake account
35    ///   3. `[w]` Pool token mint
36    ///   4. `[]` Pool stake authority
37    ///   5. `[]` Pool mint authority
38    ///   6. `[]` Rent sysvar
39    ///   7. `[]` Clock sysvar
40    ///   8. `[]` Stake history sysvar
41    ///   9. `[]` Stake config sysvar
42    ///  10. `[]` System program
43    ///  11. `[]` Token program
44    ///  12. `[]` Stake program
45    InitializePool,
46
47    ///   Restake the pool stake account if it was deactivated. This can
48    ///   happen through the stake program's `DeactivateDelinquent`
49    ///   instruction, or during a cluster restart.
50    ///
51    ///   0. `[]` Validator vote account
52    ///   1. `[]` Pool account
53    ///   2. `[w]` Pool stake account
54    ///   3. `[]` Pool stake authority
55    ///   4. `[]` Clock sysvar
56    ///   5. `[]` Stake history sysvar
57    ///   6. `[]` Stake config sysvar
58    ///   7. `[]` Stake program
59    ReactivatePoolStake,
60
61    ///   Deposit stake into the pool. The output is a "pool" token
62    ///   representing fractional ownership of the pool stake. Inputs are
63    ///   converted to the current ratio.
64    ///
65    ///   0. `[]` Pool account
66    ///   1. `[w]` Pool stake account
67    ///   2. `[w]` Pool token mint
68    ///   3. `[]` Pool stake authority
69    ///   4. `[]` Pool mint authority
70    ///   5. `[w]` User stake account to join to the pool
71    ///   6. `[w]` User account to receive pool tokens
72    ///   7. `[w]` User account to receive lamports
73    ///   8. `[]` Clock sysvar
74    ///   9. `[]` Stake history sysvar
75    ///  10. `[]` Token program
76    ///  11. `[]` Stake program
77    DepositStake,
78
79    ///   Redeem tokens issued by this pool for stake at the current ratio.
80    ///
81    ///   0. `[]` Pool account
82    ///   1. `[w]` Pool stake account
83    ///   2. `[w]` Pool token mint
84    ///   3. `[]` Pool stake authority
85    ///   4. `[]` Pool mint authority
86    ///   5. `[w]` User stake account to receive stake at
87    ///   6. `[w]` User account to take pool tokens from
88    ///   7. `[]` Clock sysvar
89    ///   8. `[]` Token program
90    ///   9. `[]` Stake program
91    WithdrawStake {
92        /// User authority for the new stake account
93        user_stake_authority: Pubkey,
94        /// Amount of tokens to redeem for stake
95        token_amount: u64,
96    },
97
98    ///   Create token metadata for the stake-pool token in the metaplex-token
99    ///   program. Step three of the permissionless three-stage initialization
100    ///   flow.
101    ///   Note this instruction is not necessary for the pool to operate, to
102    ///   ensure we cannot be broken by upstream.
103    ///
104    ///   0. `[]` Pool account
105    ///   1. `[]` Pool token mint
106    ///   2. `[]` Pool mint authority
107    ///   3. `[]` Pool MPL authority
108    ///   4. `[s, w]` Payer for creation of token metadata account
109    ///   5. `[w]` Token metadata account
110    ///   6. `[]` Metadata program id
111    ///   7. `[]` System program id
112    CreateTokenMetadata,
113
114    ///   Update token metadata for the stake-pool token in the metaplex-token
115    ///   program.
116    ///
117    ///   0. `[]` Validator vote account
118    ///   1. `[]` Pool account
119    ///   2. `[]` Pool MPL authority
120    ///   3. `[s]` Vote account authorized withdrawer
121    ///   4. `[w]` Token metadata account
122    ///   5. `[]` Metadata program id
123    UpdateTokenMetadata {
124        /// Token name
125        name: String,
126        /// Token symbol e.g. `stkSOL`
127        symbol: String,
128        /// URI of the uploaded metadata of the spl-token
129        uri: String,
130    },
131}
132
133/// Creates all necessary instructions to initialize the stake pool.
134pub fn initialize(
135    program_id: &Pubkey,
136    vote_account_address: &Pubkey,
137    payer: &Pubkey,
138    rent: &Rent,
139    minimum_delegation: u64,
140) -> Vec<Instruction> {
141    let pool_address = find_pool_address(program_id, vote_account_address);
142    let pool_rent = rent.minimum_balance(std::mem::size_of::<SinglePool>());
143
144    let stake_address = find_pool_stake_address(program_id, &pool_address);
145    let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
146    let stake_rent_plus_minimum = rent
147        .minimum_balance(stake_space)
148        .saturating_add(minimum_delegation);
149
150    let mint_address = find_pool_mint_address(program_id, &pool_address);
151    let mint_rent = rent.minimum_balance(spl_token::state::Mint::LEN);
152
153    vec![
154        system_instruction::transfer(payer, &pool_address, pool_rent),
155        system_instruction::transfer(payer, &stake_address, stake_rent_plus_minimum),
156        system_instruction::transfer(payer, &mint_address, mint_rent),
157        initialize_pool(program_id, vote_account_address),
158        create_token_metadata(program_id, &pool_address, payer),
159    ]
160}
161
162/// Creates an `InitializePool` instruction.
163pub fn initialize_pool(program_id: &Pubkey, vote_account_address: &Pubkey) -> Instruction {
164    let pool_address = find_pool_address(program_id, vote_account_address);
165    let mint_address = find_pool_mint_address(program_id, &pool_address);
166
167    let data = borsh::to_vec(&SinglePoolInstruction::InitializePool).unwrap();
168    let accounts = vec![
169        AccountMeta::new_readonly(*vote_account_address, false),
170        AccountMeta::new(pool_address, false),
171        AccountMeta::new(find_pool_stake_address(program_id, &pool_address), false),
172        AccountMeta::new(mint_address, false),
173        AccountMeta::new_readonly(
174            find_pool_stake_authority_address(program_id, &pool_address),
175            false,
176        ),
177        AccountMeta::new_readonly(
178            find_pool_mint_authority_address(program_id, &pool_address),
179            false,
180        ),
181        AccountMeta::new_readonly(sysvar::rent::id(), false),
182        AccountMeta::new_readonly(sysvar::clock::id(), false),
183        AccountMeta::new_readonly(sysvar::stake_history::id(), false),
184        #[allow(deprecated)]
185        AccountMeta::new_readonly(stake::config::id(), false),
186        AccountMeta::new_readonly(system_program::id(), false),
187        AccountMeta::new_readonly(spl_token::id(), false),
188        AccountMeta::new_readonly(stake::program::id(), false),
189    ];
190
191    Instruction {
192        program_id: *program_id,
193        accounts,
194        data,
195    }
196}
197
198/// Creates a `ReactivatePoolStake` instruction.
199pub fn reactivate_pool_stake(program_id: &Pubkey, vote_account_address: &Pubkey) -> Instruction {
200    let pool_address = find_pool_address(program_id, vote_account_address);
201
202    let data = borsh::to_vec(&SinglePoolInstruction::ReactivatePoolStake).unwrap();
203    let accounts = vec![
204        AccountMeta::new_readonly(*vote_account_address, false),
205        AccountMeta::new_readonly(pool_address, false),
206        AccountMeta::new(find_pool_stake_address(program_id, &pool_address), false),
207        AccountMeta::new_readonly(
208            find_pool_stake_authority_address(program_id, &pool_address),
209            false,
210        ),
211        AccountMeta::new_readonly(sysvar::clock::id(), false),
212        AccountMeta::new_readonly(sysvar::stake_history::id(), false),
213        #[allow(deprecated)]
214        AccountMeta::new_readonly(stake::config::id(), false),
215        AccountMeta::new_readonly(stake::program::id(), false),
216    ];
217
218    Instruction {
219        program_id: *program_id,
220        accounts,
221        data,
222    }
223}
224
225/// Creates all necessary instructions to deposit stake.
226pub fn deposit(
227    program_id: &Pubkey,
228    pool_address: &Pubkey,
229    user_stake_account: &Pubkey,
230    user_token_account: &Pubkey,
231    user_lamport_account: &Pubkey,
232    user_withdraw_authority: &Pubkey,
233) -> Vec<Instruction> {
234    let pool_stake_authority = find_pool_stake_authority_address(program_id, pool_address);
235
236    vec![
237        stake::instruction::authorize(
238            user_stake_account,
239            user_withdraw_authority,
240            &pool_stake_authority,
241            stake::state::StakeAuthorize::Staker,
242            None,
243        ),
244        stake::instruction::authorize(
245            user_stake_account,
246            user_withdraw_authority,
247            &pool_stake_authority,
248            stake::state::StakeAuthorize::Withdrawer,
249            None,
250        ),
251        deposit_stake(
252            program_id,
253            pool_address,
254            user_stake_account,
255            user_token_account,
256            user_lamport_account,
257        ),
258    ]
259}
260
261/// Creates a `DepositStake` instruction.
262pub fn deposit_stake(
263    program_id: &Pubkey,
264    pool_address: &Pubkey,
265    user_stake_account: &Pubkey,
266    user_token_account: &Pubkey,
267    user_lamport_account: &Pubkey,
268) -> Instruction {
269    let data = borsh::to_vec(&SinglePoolInstruction::DepositStake).unwrap();
270
271    let accounts = vec![
272        AccountMeta::new_readonly(*pool_address, false),
273        AccountMeta::new(find_pool_stake_address(program_id, pool_address), false),
274        AccountMeta::new(find_pool_mint_address(program_id, pool_address), false),
275        AccountMeta::new_readonly(
276            find_pool_stake_authority_address(program_id, pool_address),
277            false,
278        ),
279        AccountMeta::new_readonly(
280            find_pool_mint_authority_address(program_id, pool_address),
281            false,
282        ),
283        AccountMeta::new(*user_stake_account, false),
284        AccountMeta::new(*user_token_account, false),
285        AccountMeta::new(*user_lamport_account, false),
286        AccountMeta::new_readonly(sysvar::clock::id(), false),
287        AccountMeta::new_readonly(sysvar::stake_history::id(), false),
288        AccountMeta::new_readonly(spl_token::id(), false),
289        AccountMeta::new_readonly(stake::program::id(), false),
290    ];
291
292    Instruction {
293        program_id: *program_id,
294        accounts,
295        data,
296    }
297}
298
299/// Creates all necessary instructions to withdraw stake into a given stake
300/// account. If a new stake account is required, the user should first include
301/// `system_instruction::create_account` with account size
302/// `std::mem::size_of::<stake::state::StakeStateV2>()` and owner
303/// `stake::program::id()`.
304pub fn withdraw(
305    program_id: &Pubkey,
306    pool_address: &Pubkey,
307    user_stake_account: &Pubkey,
308    user_stake_authority: &Pubkey,
309    user_token_account: &Pubkey,
310    user_token_authority: &Pubkey,
311    token_amount: u64,
312) -> Vec<Instruction> {
313    vec![
314        spl_token::instruction::approve(
315            &spl_token::id(),
316            user_token_account,
317            &find_pool_mint_authority_address(program_id, pool_address),
318            user_token_authority,
319            &[],
320            token_amount,
321        )
322        .unwrap(),
323        withdraw_stake(
324            program_id,
325            pool_address,
326            user_stake_account,
327            user_stake_authority,
328            user_token_account,
329            token_amount,
330        ),
331    ]
332}
333
334/// Creates a `WithdrawStake` instruction.
335pub fn withdraw_stake(
336    program_id: &Pubkey,
337    pool_address: &Pubkey,
338    user_stake_account: &Pubkey,
339    user_stake_authority: &Pubkey,
340    user_token_account: &Pubkey,
341    token_amount: u64,
342) -> Instruction {
343    let data = borsh::to_vec(&SinglePoolInstruction::WithdrawStake {
344        user_stake_authority: *user_stake_authority,
345        token_amount,
346    })
347    .unwrap();
348
349    let accounts = vec![
350        AccountMeta::new_readonly(*pool_address, false),
351        AccountMeta::new(find_pool_stake_address(program_id, pool_address), false),
352        AccountMeta::new(find_pool_mint_address(program_id, pool_address), false),
353        AccountMeta::new_readonly(
354            find_pool_stake_authority_address(program_id, pool_address),
355            false,
356        ),
357        AccountMeta::new_readonly(
358            find_pool_mint_authority_address(program_id, pool_address),
359            false,
360        ),
361        AccountMeta::new(*user_stake_account, false),
362        AccountMeta::new(*user_token_account, false),
363        AccountMeta::new_readonly(sysvar::clock::id(), false),
364        AccountMeta::new_readonly(spl_token::id(), false),
365        AccountMeta::new_readonly(stake::program::id(), false),
366    ];
367
368    Instruction {
369        program_id: *program_id,
370        accounts,
371        data,
372    }
373}
374
375/// Creates necessary instructions to create and delegate a new stake account to
376/// a given validator. Uses a fixed address for each wallet and vote account
377/// combination to make it easier to find for deposits. This is an optional
378/// helper function; deposits can come from any owned stake account without
379/// lockup.
380pub fn create_and_delegate_user_stake(
381    program_id: &Pubkey,
382    vote_account_address: &Pubkey,
383    user_wallet: &Pubkey,
384    rent: &Rent,
385    stake_amount: u64,
386) -> Vec<Instruction> {
387    let pool_address = find_pool_address(program_id, vote_account_address);
388    let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
389    let lamports = rent
390        .minimum_balance(stake_space)
391        .saturating_add(stake_amount);
392    let (deposit_address, deposit_seed) =
393        find_default_deposit_account_address_and_seed(&pool_address, user_wallet);
394
395    stake::instruction::create_account_with_seed_and_delegate_stake(
396        user_wallet,
397        &deposit_address,
398        user_wallet,
399        &deposit_seed,
400        vote_account_address,
401        &stake::state::Authorized::auto(user_wallet),
402        &stake::state::Lockup::default(),
403        lamports,
404    )
405}
406
407/// Creates a `CreateTokenMetadata` instruction.
408pub fn create_token_metadata(
409    program_id: &Pubkey,
410    pool_address: &Pubkey,
411    payer: &Pubkey,
412) -> Instruction {
413    let pool_mint = find_pool_mint_address(program_id, pool_address);
414    let (token_metadata, _) = find_metadata_account(&pool_mint);
415    let data = borsh::to_vec(&SinglePoolInstruction::CreateTokenMetadata).unwrap();
416
417    let accounts = vec![
418        AccountMeta::new_readonly(*pool_address, false),
419        AccountMeta::new_readonly(pool_mint, false),
420        AccountMeta::new_readonly(
421            find_pool_mint_authority_address(program_id, pool_address),
422            false,
423        ),
424        AccountMeta::new_readonly(
425            find_pool_mpl_authority_address(program_id, pool_address),
426            false,
427        ),
428        AccountMeta::new(*payer, true),
429        AccountMeta::new(token_metadata, false),
430        AccountMeta::new_readonly(inline_mpl_token_metadata::id(), false),
431        AccountMeta::new_readonly(system_program::id(), false),
432    ];
433
434    Instruction {
435        program_id: *program_id,
436        accounts,
437        data,
438    }
439}
440
441/// Creates an `UpdateTokenMetadata` instruction.
442pub fn update_token_metadata(
443    program_id: &Pubkey,
444    vote_account_address: &Pubkey,
445    authorized_withdrawer: &Pubkey,
446    name: String,
447    symbol: String,
448    uri: String,
449) -> Instruction {
450    let pool_address = find_pool_address(program_id, vote_account_address);
451    let pool_mint = find_pool_mint_address(program_id, &pool_address);
452    let (token_metadata, _) = find_metadata_account(&pool_mint);
453    let data =
454        borsh::to_vec(&SinglePoolInstruction::UpdateTokenMetadata { name, symbol, uri }).unwrap();
455
456    let accounts = vec![
457        AccountMeta::new_readonly(*vote_account_address, false),
458        AccountMeta::new_readonly(pool_address, false),
459        AccountMeta::new_readonly(
460            find_pool_mpl_authority_address(program_id, &pool_address),
461            false,
462        ),
463        AccountMeta::new_readonly(*authorized_withdrawer, true),
464        AccountMeta::new(token_metadata, false),
465        AccountMeta::new_readonly(inline_mpl_token_metadata::id(), false),
466    ];
467
468    Instruction {
469        program_id: *program_id,
470        accounts,
471        data,
472    }
473}