solana_transaction_status/
parse_stake.rs

1use {
2    crate::parse_instruction::{
3        check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
4    },
5    bincode::deserialize,
6    serde_json::{json, Map, Value},
7    solana_message::{compiled_instruction::CompiledInstruction, AccountKeys},
8    solana_program::stake::instruction::StakeInstruction,
9};
10
11pub fn parse_stake(
12    instruction: &CompiledInstruction,
13    account_keys: &AccountKeys,
14) -> Result<ParsedInstructionEnum, ParseInstructionError> {
15    let stake_instruction: StakeInstruction = deserialize(&instruction.data)
16        .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::Stake))?;
17    match instruction.accounts.iter().max() {
18        Some(index) if (*index as usize) < account_keys.len() => {}
19        _ => {
20            // Runtime should prevent this from ever happening
21            return Err(ParseInstructionError::InstructionKeyMismatch(
22                ParsableProgram::Stake,
23            ));
24        }
25    }
26    match stake_instruction {
27        StakeInstruction::Initialize(authorized, lockup) => {
28            check_num_stake_accounts(&instruction.accounts, 2)?;
29            let authorized = json!({
30                "staker": authorized.staker.to_string(),
31                "withdrawer": authorized.withdrawer.to_string(),
32            });
33            let lockup = json!({
34                "unixTimestamp": lockup.unix_timestamp,
35                "epoch": lockup.epoch,
36                "custodian": lockup.custodian.to_string(),
37            });
38            Ok(ParsedInstructionEnum {
39                instruction_type: "initialize".to_string(),
40                info: json!({
41                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
42                    "rentSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
43                    "authorized": authorized,
44                    "lockup": lockup,
45                }),
46            })
47        }
48        StakeInstruction::Authorize(new_authorized, authority_type) => {
49            check_num_stake_accounts(&instruction.accounts, 3)?;
50            let mut value = json!({
51                "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
52                "clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
53                "authority": account_keys[instruction.accounts[2] as usize].to_string(),
54                "newAuthority": new_authorized.to_string(),
55                "authorityType": authority_type,
56            });
57            let map = value.as_object_mut().unwrap();
58            if instruction.accounts.len() >= 4 {
59                map.insert(
60                    "custodian".to_string(),
61                    json!(account_keys[instruction.accounts[3] as usize].to_string()),
62                );
63            }
64            Ok(ParsedInstructionEnum {
65                instruction_type: "authorize".to_string(),
66                info: value,
67            })
68        }
69        StakeInstruction::DelegateStake => {
70            check_num_stake_accounts(&instruction.accounts, 6)?;
71            Ok(ParsedInstructionEnum {
72                instruction_type: "delegate".to_string(),
73                info: json!({
74                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
75                    "voteAccount": account_keys[instruction.accounts[1] as usize].to_string(),
76                    "clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
77                    "stakeHistorySysvar": account_keys[instruction.accounts[3] as usize].to_string(),
78                    "stakeConfigAccount": account_keys[instruction.accounts[4] as usize].to_string(),
79                    "stakeAuthority": account_keys[instruction.accounts[5] as usize].to_string(),
80                }),
81            })
82        }
83        StakeInstruction::Split(lamports) => {
84            check_num_stake_accounts(&instruction.accounts, 3)?;
85            Ok(ParsedInstructionEnum {
86                instruction_type: "split".to_string(),
87                info: json!({
88                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
89                    "newSplitAccount": account_keys[instruction.accounts[1] as usize].to_string(),
90                    "stakeAuthority": account_keys[instruction.accounts[2] as usize].to_string(),
91                    "lamports": lamports,
92                }),
93            })
94        }
95        StakeInstruction::Withdraw(lamports) => {
96            check_num_stake_accounts(&instruction.accounts, 5)?;
97            let mut value = json!({
98                "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
99                "destination": account_keys[instruction.accounts[1] as usize].to_string(),
100                "clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
101                "stakeHistorySysvar": account_keys[instruction.accounts[3] as usize].to_string(),
102                "withdrawAuthority": account_keys[instruction.accounts[4] as usize].to_string(),
103                "lamports": lamports,
104            });
105            let map = value.as_object_mut().unwrap();
106            if instruction.accounts.len() >= 6 {
107                map.insert(
108                    "custodian".to_string(),
109                    json!(account_keys[instruction.accounts[5] as usize].to_string()),
110                );
111            }
112            Ok(ParsedInstructionEnum {
113                instruction_type: "withdraw".to_string(),
114                info: value,
115            })
116        }
117        StakeInstruction::Deactivate => {
118            check_num_stake_accounts(&instruction.accounts, 3)?;
119            Ok(ParsedInstructionEnum {
120                instruction_type: "deactivate".to_string(),
121                info: json!({
122                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
123                    "clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
124                    "stakeAuthority": account_keys[instruction.accounts[2] as usize].to_string(),
125                }),
126            })
127        }
128        StakeInstruction::SetLockup(lockup_args) => {
129            check_num_stake_accounts(&instruction.accounts, 2)?;
130            let mut lockup_map = Map::new();
131            if let Some(timestamp) = lockup_args.unix_timestamp {
132                lockup_map.insert("unixTimestamp".to_string(), json!(timestamp));
133            }
134            if let Some(epoch) = lockup_args.epoch {
135                lockup_map.insert("epoch".to_string(), json!(epoch));
136            }
137            if let Some(custodian) = lockup_args.custodian {
138                lockup_map.insert("custodian".to_string(), json!(custodian.to_string()));
139            }
140            Ok(ParsedInstructionEnum {
141                instruction_type: "setLockup".to_string(),
142                info: json!({
143                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
144                    "custodian": account_keys[instruction.accounts[1] as usize].to_string(),
145                    "lockup": lockup_map,
146                }),
147            })
148        }
149        StakeInstruction::Merge => {
150            check_num_stake_accounts(&instruction.accounts, 5)?;
151            Ok(ParsedInstructionEnum {
152                instruction_type: "merge".to_string(),
153                info: json!({
154                    "destination": account_keys[instruction.accounts[0] as usize].to_string(),
155                    "source": account_keys[instruction.accounts[1] as usize].to_string(),
156                    "clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
157                    "stakeHistorySysvar": account_keys[instruction.accounts[3] as usize].to_string(),
158                    "stakeAuthority": account_keys[instruction.accounts[4] as usize].to_string(),
159                }),
160            })
161        }
162        StakeInstruction::AuthorizeWithSeed(args) => {
163            check_num_stake_accounts(&instruction.accounts, 2)?;
164            let mut value = json!({
165                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
166                    "authorityBase": account_keys[instruction.accounts[1] as usize].to_string(),
167                    "newAuthorized": args.new_authorized_pubkey.to_string(),
168                    "authorityType": args.stake_authorize,
169                    "authoritySeed": args.authority_seed,
170                    "authorityOwner": args.authority_owner.to_string(),
171            });
172            let map = value.as_object_mut().unwrap();
173            if instruction.accounts.len() >= 3 {
174                map.insert(
175                    "clockSysvar".to_string(),
176                    json!(account_keys[instruction.accounts[2] as usize].to_string()),
177                );
178            }
179            if instruction.accounts.len() >= 4 {
180                map.insert(
181                    "custodian".to_string(),
182                    json!(account_keys[instruction.accounts[3] as usize].to_string()),
183                );
184            }
185            Ok(ParsedInstructionEnum {
186                instruction_type: "authorizeWithSeed".to_string(),
187                info: value,
188            })
189        }
190        StakeInstruction::InitializeChecked => {
191            check_num_stake_accounts(&instruction.accounts, 4)?;
192            Ok(ParsedInstructionEnum {
193                instruction_type: "initializeChecked".to_string(),
194                info: json!({
195                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
196                    "rentSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
197                    "staker": account_keys[instruction.accounts[2] as usize].to_string(),
198                    "withdrawer": account_keys[instruction.accounts[3] as usize].to_string(),
199                }),
200            })
201        }
202        StakeInstruction::AuthorizeChecked(authority_type) => {
203            check_num_stake_accounts(&instruction.accounts, 4)?;
204            let mut value = json!({
205                "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
206                "clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
207                "authority": account_keys[instruction.accounts[2] as usize].to_string(),
208                "newAuthority": account_keys[instruction.accounts[3] as usize].to_string(),
209                "authorityType": authority_type,
210            });
211            let map = value.as_object_mut().unwrap();
212            if instruction.accounts.len() >= 5 {
213                map.insert(
214                    "custodian".to_string(),
215                    json!(account_keys[instruction.accounts[4] as usize].to_string()),
216                );
217            }
218            Ok(ParsedInstructionEnum {
219                instruction_type: "authorizeChecked".to_string(),
220                info: value,
221            })
222        }
223        StakeInstruction::AuthorizeCheckedWithSeed(args) => {
224            check_num_stake_accounts(&instruction.accounts, 4)?;
225            let mut value = json!({
226                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
227                    "authorityBase": account_keys[instruction.accounts[1] as usize].to_string(),
228                    "clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
229                    "newAuthorized": account_keys[instruction.accounts[3] as usize].to_string(),
230                    "authorityType": args.stake_authorize,
231                    "authoritySeed": args.authority_seed,
232                    "authorityOwner": args.authority_owner.to_string(),
233            });
234            let map = value.as_object_mut().unwrap();
235            if instruction.accounts.len() >= 5 {
236                map.insert(
237                    "custodian".to_string(),
238                    json!(account_keys[instruction.accounts[4] as usize].to_string()),
239                );
240            }
241            Ok(ParsedInstructionEnum {
242                instruction_type: "authorizeCheckedWithSeed".to_string(),
243                info: value,
244            })
245        }
246        StakeInstruction::SetLockupChecked(lockup_args) => {
247            check_num_stake_accounts(&instruction.accounts, 2)?;
248            let mut lockup_map = Map::new();
249            if let Some(timestamp) = lockup_args.unix_timestamp {
250                lockup_map.insert("unixTimestamp".to_string(), json!(timestamp));
251            }
252            if let Some(epoch) = lockup_args.epoch {
253                lockup_map.insert("epoch".to_string(), json!(epoch));
254            }
255            if instruction.accounts.len() >= 3 {
256                lockup_map.insert(
257                    "custodian".to_string(),
258                    json!(account_keys[instruction.accounts[2] as usize].to_string()),
259                );
260            }
261            Ok(ParsedInstructionEnum {
262                instruction_type: "setLockupChecked".to_string(),
263                info: json!({
264                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
265                    "custodian": account_keys[instruction.accounts[1] as usize].to_string(),
266                    "lockup": lockup_map,
267                }),
268            })
269        }
270        StakeInstruction::GetMinimumDelegation => Ok(ParsedInstructionEnum {
271            instruction_type: "getMinimumDelegation".to_string(),
272            info: Value::default(),
273        }),
274        StakeInstruction::DeactivateDelinquent => {
275            check_num_stake_accounts(&instruction.accounts, 3)?;
276            Ok(ParsedInstructionEnum {
277                instruction_type: "deactivateDelinquent".to_string(),
278                info: json!({
279                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
280                    "voteAccount": account_keys[instruction.accounts[1] as usize].to_string(),
281                    "referenceVoteAccount": account_keys[instruction.accounts[2] as usize].to_string(),
282                }),
283            })
284        }
285        #[allow(deprecated)]
286        StakeInstruction::Redelegate => {
287            check_num_stake_accounts(&instruction.accounts, 5)?;
288            Ok(ParsedInstructionEnum {
289                instruction_type: "redelegate".to_string(),
290                info: json!({
291                    "stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
292                    "newStakeAccount": account_keys[instruction.accounts[1] as usize].to_string(),
293                    "voteAccount": account_keys[instruction.accounts[2] as usize].to_string(),
294                    "stakeConfigAccount": account_keys[instruction.accounts[3] as usize].to_string(),
295                    "stakeAuthority": account_keys[instruction.accounts[4] as usize].to_string(),
296                }),
297            })
298        }
299        StakeInstruction::MoveStake(lamports) => {
300            check_num_stake_accounts(&instruction.accounts, 3)?;
301            Ok(ParsedInstructionEnum {
302                instruction_type: "moveStake".to_string(),
303                info: json!({
304                    "source": account_keys[instruction.accounts[0] as usize].to_string(),
305                    "destination": account_keys[instruction.accounts[1] as usize].to_string(),
306                    "stakeAuthority": account_keys[instruction.accounts[2] as usize].to_string(),
307                    "lamports": lamports,
308                }),
309            })
310        }
311        StakeInstruction::MoveLamports(lamports) => {
312            check_num_stake_accounts(&instruction.accounts, 3)?;
313            Ok(ParsedInstructionEnum {
314                instruction_type: "moveLamports".to_string(),
315                info: json!({
316                    "source": account_keys[instruction.accounts[0] as usize].to_string(),
317                    "destination": account_keys[instruction.accounts[1] as usize].to_string(),
318                    "stakeAuthority": account_keys[instruction.accounts[2] as usize].to_string(),
319                    "lamports": lamports,
320                }),
321            })
322        }
323    }
324}
325
326fn check_num_stake_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInstructionError> {
327    check_num_accounts(accounts, num, ParsableProgram::Stake)
328}
329
330#[cfg(test)]
331mod test {
332    use {
333        super::*,
334        solana_instruction::Instruction,
335        solana_message::Message,
336        solana_program::stake::{
337            config,
338            instruction::{self, LockupArgs},
339            state::{Authorized, Lockup, StakeAuthorize},
340        },
341        solana_pubkey::Pubkey,
342        solana_sdk_ids::sysvar,
343        std::iter::repeat_with,
344    };
345
346    #[test]
347    fn test_parse_stake_initialize_ix() {
348        let from_pubkey = Pubkey::new_unique();
349        let stake_pubkey = Pubkey::new_unique();
350        let authorized = Authorized {
351            staker: Pubkey::new_unique(),
352            withdrawer: Pubkey::new_unique(),
353        };
354        let lockup = Lockup {
355            unix_timestamp: 1_234_567_890,
356            epoch: 11,
357            custodian: Pubkey::new_unique(),
358        };
359        let lamports = 55;
360
361        let instructions = instruction::create_account(
362            &from_pubkey,
363            &stake_pubkey,
364            &authorized,
365            &lockup,
366            lamports,
367        );
368        let mut message = Message::new(&instructions, None);
369        assert_eq!(
370            parse_stake(
371                &message.instructions[1],
372                &AccountKeys::new(&message.account_keys, None)
373            )
374            .unwrap(),
375            ParsedInstructionEnum {
376                instruction_type: "initialize".to_string(),
377                info: json!({
378                    "stakeAccount": stake_pubkey.to_string(),
379                    "rentSysvar": sysvar::rent::ID.to_string(),
380                    "authorized": {
381                        "staker": authorized.staker.to_string(),
382                        "withdrawer": authorized.withdrawer.to_string(),
383                    },
384                    "lockup": {
385                        "unixTimestamp": lockup.unix_timestamp,
386                        "epoch": lockup.epoch,
387                        "custodian": lockup.custodian.to_string(),
388                    }
389                }),
390            }
391        );
392        assert!(parse_stake(
393            &message.instructions[1],
394            &AccountKeys::new(&message.account_keys[0..2], None)
395        )
396        .is_err());
397        let keys = message.account_keys.clone();
398        message.instructions[0].accounts.pop();
399        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
400    }
401
402    #[test]
403    fn test_parse_stake_authorize_ix() {
404        let stake_pubkey = Pubkey::new_unique();
405        let authorized_pubkey = Pubkey::new_unique();
406        let new_authorized_pubkey = Pubkey::new_unique();
407        let custodian_pubkey = Pubkey::new_unique();
408        let instruction = instruction::authorize(
409            &stake_pubkey,
410            &authorized_pubkey,
411            &new_authorized_pubkey,
412            StakeAuthorize::Staker,
413            None,
414        );
415        let mut message = Message::new(&[instruction], None);
416        assert_eq!(
417            parse_stake(
418                &message.instructions[0],
419                &AccountKeys::new(&message.account_keys, None)
420            )
421            .unwrap(),
422            ParsedInstructionEnum {
423                instruction_type: "authorize".to_string(),
424                info: json!({
425                    "stakeAccount": stake_pubkey.to_string(),
426                    "clockSysvar": sysvar::clock::ID.to_string(),
427                    "authority": authorized_pubkey.to_string(),
428                    "newAuthority": new_authorized_pubkey.to_string(),
429                    "authorityType": StakeAuthorize::Staker,
430                }),
431            }
432        );
433        assert!(parse_stake(
434            &message.instructions[0],
435            &AccountKeys::new(&message.account_keys[0..2], None)
436        )
437        .is_err());
438        let keys = message.account_keys.clone();
439        message.instructions[0].accounts.pop();
440        message.instructions[0].accounts.pop();
441        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
442
443        let instruction = instruction::authorize(
444            &stake_pubkey,
445            &authorized_pubkey,
446            &new_authorized_pubkey,
447            StakeAuthorize::Withdrawer,
448            Some(&custodian_pubkey),
449        );
450        let mut message = Message::new(&[instruction], None);
451        assert_eq!(
452            parse_stake(
453                &message.instructions[0],
454                &AccountKeys::new(&message.account_keys, None)
455            )
456            .unwrap(),
457            ParsedInstructionEnum {
458                instruction_type: "authorize".to_string(),
459                info: json!({
460                    "stakeAccount": stake_pubkey.to_string(),
461                    "clockSysvar": sysvar::clock::ID.to_string(),
462                    "authority": authorized_pubkey.to_string(),
463                    "newAuthority": new_authorized_pubkey.to_string(),
464                    "authorityType": StakeAuthorize::Withdrawer,
465                    "custodian": custodian_pubkey.to_string(),
466                }),
467            }
468        );
469        assert!(parse_stake(
470            &message.instructions[0],
471            &AccountKeys::new(&message.account_keys[0..2], None)
472        )
473        .is_err());
474        let keys = message.account_keys.clone();
475        message.instructions[0].accounts.pop();
476        message.instructions[0].accounts.pop();
477        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
478    }
479
480    #[test]
481    fn test_parse_stake_delegate_ix() {
482        let stake_pubkey = Pubkey::new_unique();
483        let authorized_pubkey = Pubkey::new_unique();
484        let vote_pubkey = Pubkey::new_unique();
485        let instruction =
486            instruction::delegate_stake(&stake_pubkey, &authorized_pubkey, &vote_pubkey);
487        let mut message = Message::new(&[instruction], None);
488        assert_eq!(
489            parse_stake(
490                &message.instructions[0],
491                &AccountKeys::new(&message.account_keys, None)
492            )
493            .unwrap(),
494            ParsedInstructionEnum {
495                instruction_type: "delegate".to_string(),
496                info: json!({
497                    "stakeAccount": stake_pubkey.to_string(),
498                    "voteAccount": vote_pubkey.to_string(),
499                    "clockSysvar": sysvar::clock::ID.to_string(),
500                    "stakeHistorySysvar": sysvar::stake_history::ID.to_string(),
501                    "stakeConfigAccount": config::ID.to_string(),
502                    "stakeAuthority": authorized_pubkey.to_string(),
503                }),
504            }
505        );
506        assert!(parse_stake(
507            &message.instructions[0],
508            &AccountKeys::new(&message.account_keys[0..5], None)
509        )
510        .is_err());
511        let keys = message.account_keys.clone();
512        message.instructions[0].accounts.pop();
513        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
514    }
515
516    #[test]
517    fn test_parse_stake_split_ix() {
518        let lamports = 55;
519        let stake_pubkey = Pubkey::new_unique();
520        let authorized_pubkey = Pubkey::new_unique();
521        let split_stake_pubkey = Pubkey::new_unique();
522        let instructions = instruction::split(
523            &stake_pubkey,
524            &authorized_pubkey,
525            lamports,
526            &split_stake_pubkey,
527        );
528        let mut message = Message::new(&instructions, None);
529        assert_eq!(
530            parse_stake(
531                &message.instructions[2],
532                &AccountKeys::new(&message.account_keys, None)
533            )
534            .unwrap(),
535            ParsedInstructionEnum {
536                instruction_type: "split".to_string(),
537                info: json!({
538                    "stakeAccount": stake_pubkey.to_string(),
539                    "newSplitAccount": split_stake_pubkey.to_string(),
540                    "stakeAuthority": authorized_pubkey.to_string(),
541                    "lamports": lamports,
542                }),
543            }
544        );
545        assert!(parse_stake(
546            &message.instructions[2],
547            &AccountKeys::new(&message.account_keys[0..2], None)
548        )
549        .is_err());
550        let keys = message.account_keys.clone();
551        message.instructions[0].accounts.pop();
552        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
553    }
554
555    #[test]
556    fn test_parse_stake_withdraw_ix() {
557        let lamports = 55;
558        let stake_pubkey = Pubkey::new_unique();
559        let withdrawer_pubkey = Pubkey::new_unique();
560        let to_pubkey = Pubkey::new_unique();
561        let custodian_pubkey = Pubkey::new_unique();
562        let instruction = instruction::withdraw(
563            &stake_pubkey,
564            &withdrawer_pubkey,
565            &to_pubkey,
566            lamports,
567            None,
568        );
569        let message = Message::new(&[instruction], None);
570        assert_eq!(
571            parse_stake(
572                &message.instructions[0],
573                &AccountKeys::new(&message.account_keys, None)
574            )
575            .unwrap(),
576            ParsedInstructionEnum {
577                instruction_type: "withdraw".to_string(),
578                info: json!({
579                    "stakeAccount": stake_pubkey.to_string(),
580                    "destination": to_pubkey.to_string(),
581                    "clockSysvar": sysvar::clock::ID.to_string(),
582                    "stakeHistorySysvar": sysvar::stake_history::ID.to_string(),
583                    "withdrawAuthority": withdrawer_pubkey.to_string(),
584                    "lamports": lamports,
585                }),
586            }
587        );
588        let instruction = instruction::withdraw(
589            &stake_pubkey,
590            &withdrawer_pubkey,
591            &to_pubkey,
592            lamports,
593            Some(&custodian_pubkey),
594        );
595        let mut message = Message::new(&[instruction], None);
596        assert_eq!(
597            parse_stake(
598                &message.instructions[0],
599                &AccountKeys::new(&message.account_keys, None)
600            )
601            .unwrap(),
602            ParsedInstructionEnum {
603                instruction_type: "withdraw".to_string(),
604                info: json!({
605                    "stakeAccount": stake_pubkey.to_string(),
606                    "destination": to_pubkey.to_string(),
607                    "clockSysvar": sysvar::clock::ID.to_string(),
608                    "stakeHistorySysvar": sysvar::stake_history::ID.to_string(),
609                    "withdrawAuthority": withdrawer_pubkey.to_string(),
610                    "custodian": custodian_pubkey.to_string(),
611                    "lamports": lamports,
612                }),
613            }
614        );
615        assert!(parse_stake(
616            &message.instructions[0],
617            &AccountKeys::new(&message.account_keys[0..4], None)
618        )
619        .is_err());
620        let keys = message.account_keys.clone();
621        message.instructions[0].accounts.pop();
622        message.instructions[0].accounts.pop();
623        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
624    }
625
626    #[test]
627    fn test_parse_stake_deactivate_stake_ix() {
628        let stake_pubkey = Pubkey::new_unique();
629        let authorized_pubkey = Pubkey::new_unique();
630        let instruction = instruction::deactivate_stake(&stake_pubkey, &authorized_pubkey);
631        let mut message = Message::new(&[instruction], None);
632        assert_eq!(
633            parse_stake(
634                &message.instructions[0],
635                &AccountKeys::new(&message.account_keys, None)
636            )
637            .unwrap(),
638            ParsedInstructionEnum {
639                instruction_type: "deactivate".to_string(),
640                info: json!({
641                    "stakeAccount": stake_pubkey.to_string(),
642                    "clockSysvar": sysvar::clock::ID.to_string(),
643                    "stakeAuthority": authorized_pubkey.to_string(),
644                }),
645            }
646        );
647        assert!(parse_stake(
648            &message.instructions[0],
649            &AccountKeys::new(&message.account_keys[0..2], None)
650        )
651        .is_err());
652        let keys = message.account_keys.clone();
653        message.instructions[0].accounts.pop();
654        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
655    }
656
657    #[test]
658    fn test_parse_stake_merge_ix() {
659        let destination_stake_pubkey = Pubkey::new_unique();
660        let source_stake_pubkey = Pubkey::new_unique();
661        let authorized_pubkey = Pubkey::new_unique();
662        let instructions = instruction::merge(
663            &destination_stake_pubkey,
664            &source_stake_pubkey,
665            &authorized_pubkey,
666        );
667        let mut message = Message::new(&instructions, None);
668        assert_eq!(
669            parse_stake(
670                &message.instructions[0],
671                &AccountKeys::new(&message.account_keys, None)
672            )
673            .unwrap(),
674            ParsedInstructionEnum {
675                instruction_type: "merge".to_string(),
676                info: json!({
677                    "destination": destination_stake_pubkey.to_string(),
678                    "source": source_stake_pubkey.to_string(),
679                    "clockSysvar": sysvar::clock::ID.to_string(),
680                    "stakeHistorySysvar": sysvar::stake_history::ID.to_string(),
681                    "stakeAuthority": authorized_pubkey.to_string(),
682                }),
683            }
684        );
685        assert!(parse_stake(
686            &message.instructions[0],
687            &AccountKeys::new(&message.account_keys[0..4], None)
688        )
689        .is_err());
690        let keys = message.account_keys.clone();
691        message.instructions[0].accounts.pop();
692        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
693    }
694
695    #[test]
696    fn test_parse_stake_authorize_with_seed_ix() {
697        let stake_pubkey = Pubkey::new_unique();
698        let authority_base_pubkey = Pubkey::new_unique();
699        let authority_owner_pubkey = Pubkey::new_unique();
700        let new_authorized_pubkey = Pubkey::new_unique();
701        let custodian_pubkey = Pubkey::new_unique();
702
703        let seed = "test_seed";
704        let instruction = instruction::authorize_with_seed(
705            &stake_pubkey,
706            &authority_base_pubkey,
707            seed.to_string(),
708            &authority_owner_pubkey,
709            &new_authorized_pubkey,
710            StakeAuthorize::Staker,
711            None,
712        );
713        let mut message = Message::new(&[instruction], None);
714        assert_eq!(
715            parse_stake(
716                &message.instructions[0],
717                &AccountKeys::new(&message.account_keys, None)
718            )
719            .unwrap(),
720            ParsedInstructionEnum {
721                instruction_type: "authorizeWithSeed".to_string(),
722                info: json!({
723                    "stakeAccount": stake_pubkey.to_string(),
724                    "authorityOwner": authority_owner_pubkey.to_string(),
725                    "newAuthorized": new_authorized_pubkey.to_string(),
726                    "authorityBase": authority_base_pubkey.to_string(),
727                    "authoritySeed": seed,
728                    "authorityType": StakeAuthorize::Staker,
729                    "clockSysvar": sysvar::clock::ID.to_string(),
730                }),
731            }
732        );
733        assert!(parse_stake(
734            &message.instructions[0],
735            &AccountKeys::new(&message.account_keys[0..2], None)
736        )
737        .is_err());
738        let keys = message.account_keys.clone();
739        message.instructions[0].accounts.pop();
740        message.instructions[0].accounts.pop();
741        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
742
743        let instruction = instruction::authorize_with_seed(
744            &stake_pubkey,
745            &authority_base_pubkey,
746            seed.to_string(),
747            &authority_owner_pubkey,
748            &new_authorized_pubkey,
749            StakeAuthorize::Withdrawer,
750            Some(&custodian_pubkey),
751        );
752        let mut message = Message::new(&[instruction], None);
753        assert_eq!(
754            parse_stake(
755                &message.instructions[0],
756                &AccountKeys::new(&message.account_keys, None)
757            )
758            .unwrap(),
759            ParsedInstructionEnum {
760                instruction_type: "authorizeWithSeed".to_string(),
761                info: json!({
762                    "stakeAccount": stake_pubkey.to_string(),
763                    "authorityOwner": authority_owner_pubkey.to_string(),
764                    "newAuthorized": new_authorized_pubkey.to_string(),
765                    "authorityBase": authority_base_pubkey.to_string(),
766                    "authoritySeed": seed,
767                    "authorityType": StakeAuthorize::Withdrawer,
768                    "clockSysvar": sysvar::clock::ID.to_string(),
769                    "custodian": custodian_pubkey.to_string(),
770                }),
771            }
772        );
773        assert!(parse_stake(
774            &message.instructions[0],
775            &AccountKeys::new(&message.account_keys[0..3], None)
776        )
777        .is_err());
778        let keys = message.account_keys.clone();
779        message.instructions[0].accounts.pop();
780        message.instructions[0].accounts.pop();
781        message.instructions[0].accounts.pop();
782        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
783    }
784
785    #[test]
786    fn test_parse_stake_set_lockup() {
787        let keys: Vec<Pubkey> = repeat_with(Pubkey::new_unique).take(3).collect();
788        let unix_timestamp = 1_234_567_890;
789        let epoch = 11;
790        let custodian = Pubkey::new_unique();
791
792        let lockup = LockupArgs {
793            unix_timestamp: Some(unix_timestamp),
794            epoch: None,
795            custodian: None,
796        };
797        let instruction = instruction::set_lockup(&keys[1], &lockup, &keys[0]);
798        let message = Message::new(&[instruction], None);
799        assert_eq!(
800            parse_stake(
801                &message.instructions[0],
802                &AccountKeys::new(&keys[0..2], None)
803            )
804            .unwrap(),
805            ParsedInstructionEnum {
806                instruction_type: "setLockup".to_string(),
807                info: json!({
808                    "stakeAccount": keys[1].to_string(),
809                    "custodian": keys[0].to_string(),
810                    "lockup": {
811                        "unixTimestamp": unix_timestamp
812                    }
813                }),
814            }
815        );
816
817        let lockup = LockupArgs {
818            unix_timestamp: Some(unix_timestamp),
819            epoch: Some(epoch),
820            custodian: None,
821        };
822        let instruction = instruction::set_lockup(&keys[1], &lockup, &keys[0]);
823        let message = Message::new(&[instruction], None);
824        assert_eq!(
825            parse_stake(
826                &message.instructions[0],
827                &AccountKeys::new(&keys[0..2], None)
828            )
829            .unwrap(),
830            ParsedInstructionEnum {
831                instruction_type: "setLockup".to_string(),
832                info: json!({
833                    "stakeAccount": keys[1].to_string(),
834                    "custodian": keys[0].to_string(),
835                    "lockup": {
836                        "unixTimestamp": unix_timestamp,
837                        "epoch": epoch,
838                    }
839                }),
840            }
841        );
842
843        let lockup = LockupArgs {
844            unix_timestamp: Some(unix_timestamp),
845            epoch: Some(epoch),
846            custodian: Some(custodian),
847        };
848        let instruction = instruction::set_lockup(&keys[1], &lockup, &keys[0]);
849        let mut message = Message::new(&[instruction], None);
850        assert_eq!(
851            parse_stake(
852                &message.instructions[0],
853                &AccountKeys::new(&keys[0..2], None)
854            )
855            .unwrap(),
856            ParsedInstructionEnum {
857                instruction_type: "setLockup".to_string(),
858                info: json!({
859                    "stakeAccount": keys[1].to_string(),
860                    "custodian": keys[0].to_string(),
861                    "lockup": {
862                        "unixTimestamp": unix_timestamp,
863                        "epoch": epoch,
864                        "custodian": custodian.to_string(),
865                    }
866                }),
867            }
868        );
869
870        assert!(parse_stake(
871            &message.instructions[0],
872            &AccountKeys::new(&keys[0..1], None)
873        )
874        .is_err());
875        let keys = message.account_keys.clone();
876        message.instructions[0].accounts.pop();
877        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
878
879        let lockup = LockupArgs {
880            unix_timestamp: Some(unix_timestamp),
881            epoch: None,
882            custodian: None,
883        };
884        let instruction = instruction::set_lockup_checked(&keys[1], &lockup, &keys[0]);
885        let message = Message::new(&[instruction], None);
886        assert_eq!(
887            parse_stake(
888                &message.instructions[0],
889                &AccountKeys::new(&keys[0..2], None)
890            )
891            .unwrap(),
892            ParsedInstructionEnum {
893                instruction_type: "setLockupChecked".to_string(),
894                info: json!({
895                    "stakeAccount": keys[1].to_string(),
896                    "custodian": keys[0].to_string(),
897                    "lockup": {
898                        "unixTimestamp": unix_timestamp
899                    }
900                }),
901            }
902        );
903
904        let lockup = LockupArgs {
905            unix_timestamp: Some(unix_timestamp),
906            epoch: Some(epoch),
907            custodian: None,
908        };
909        let instruction = instruction::set_lockup_checked(&keys[1], &lockup, &keys[0]);
910        let mut message = Message::new(&[instruction], None);
911        assert_eq!(
912            parse_stake(
913                &message.instructions[0],
914                &AccountKeys::new(&keys[0..2], None)
915            )
916            .unwrap(),
917            ParsedInstructionEnum {
918                instruction_type: "setLockupChecked".to_string(),
919                info: json!({
920                    "stakeAccount": keys[1].to_string(),
921                    "custodian": keys[0].to_string(),
922                    "lockup": {
923                        "unixTimestamp": unix_timestamp,
924                        "epoch": epoch,
925                    }
926                }),
927            }
928        );
929        assert!(parse_stake(
930            &message.instructions[0],
931            &AccountKeys::new(&keys[0..1], None)
932        )
933        .is_err());
934        let keys = message.account_keys.clone();
935        message.instructions[0].accounts.pop();
936        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
937
938        let lockup = LockupArgs {
939            unix_timestamp: Some(unix_timestamp),
940            epoch: Some(epoch),
941            custodian: Some(keys[1]),
942        };
943        let instruction = instruction::set_lockup_checked(&keys[2], &lockup, &keys[0]);
944        let mut message = Message::new(&[instruction], None);
945        assert_eq!(
946            parse_stake(
947                &message.instructions[0],
948                &AccountKeys::new(&keys[0..3], None)
949            )
950            .unwrap(),
951            ParsedInstructionEnum {
952                instruction_type: "setLockupChecked".to_string(),
953                info: json!({
954                    "stakeAccount": keys[2].to_string(),
955                    "custodian": keys[0].to_string(),
956                    "lockup": {
957                        "unixTimestamp": unix_timestamp,
958                        "epoch": epoch,
959                        "custodian": keys[1].to_string(),
960                    }
961                }),
962            }
963        );
964        assert!(parse_stake(
965            &message.instructions[0],
966            &AccountKeys::new(&keys[0..2], None)
967        )
968        .is_err());
969        let keys = message.account_keys.clone();
970        message.instructions[0].accounts.pop();
971        message.instructions[0].accounts.pop();
972        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
973    }
974
975    #[test]
976    fn test_parse_stake_create_account_checked_ix() {
977        let from_pubkey = Pubkey::new_unique();
978        let stake_pubkey = Pubkey::new_unique();
979
980        let authorized = Authorized {
981            staker: Pubkey::new_unique(),
982            withdrawer: Pubkey::new_unique(),
983        };
984        let lamports = 55;
985
986        let instructions =
987            instruction::create_account_checked(&from_pubkey, &stake_pubkey, &authorized, lamports);
988        let mut message = Message::new(&instructions, None);
989        assert_eq!(
990            parse_stake(
991                &message.instructions[1],
992                &AccountKeys::new(&message.account_keys, None)
993            )
994            .unwrap(),
995            ParsedInstructionEnum {
996                instruction_type: "initializeChecked".to_string(),
997                info: json!({
998                    "stakeAccount": stake_pubkey.to_string(),
999                    "rentSysvar": sysvar::rent::ID.to_string(),
1000                    "staker": authorized.staker.to_string(),
1001                    "withdrawer": authorized.withdrawer.to_string(),
1002                }),
1003            }
1004        );
1005        assert!(parse_stake(
1006            &message.instructions[1],
1007            &AccountKeys::new(&message.account_keys[0..3], None)
1008        )
1009        .is_err());
1010        let keys = message.account_keys.clone();
1011        message.instructions[0].accounts.pop();
1012        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
1013    }
1014
1015    #[test]
1016    fn test_parse_stake_authorize_checked_ix() {
1017        let stake_pubkey = Pubkey::new_unique();
1018        let authorized_pubkey = Pubkey::new_unique();
1019        let new_authorized_pubkey = Pubkey::new_unique();
1020        let custodian_pubkey = Pubkey::new_unique();
1021
1022        let instruction = instruction::authorize_checked(
1023            &stake_pubkey,
1024            &authorized_pubkey,
1025            &new_authorized_pubkey,
1026            StakeAuthorize::Staker,
1027            None,
1028        );
1029        let mut message = Message::new(&[instruction], None);
1030        assert_eq!(
1031            parse_stake(
1032                &message.instructions[0],
1033                &AccountKeys::new(&message.account_keys, None)
1034            )
1035            .unwrap(),
1036            ParsedInstructionEnum {
1037                instruction_type: "authorizeChecked".to_string(),
1038                info: json!({
1039                    "stakeAccount": stake_pubkey.to_string(),
1040                    "clockSysvar": sysvar::clock::ID.to_string(),
1041                    "authority": authorized_pubkey.to_string(),
1042                    "newAuthority": new_authorized_pubkey.to_string(),
1043                    "authorityType": StakeAuthorize::Staker,
1044                }),
1045            }
1046        );
1047        assert!(parse_stake(
1048            &message.instructions[0],
1049            &AccountKeys::new(&message.account_keys[0..3], None)
1050        )
1051        .is_err());
1052        let keys = message.account_keys.clone();
1053        message.instructions[0].accounts.pop();
1054        message.instructions[0].accounts.pop();
1055        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
1056
1057        let instruction = instruction::authorize_checked(
1058            &stake_pubkey,
1059            &authorized_pubkey,
1060            &new_authorized_pubkey,
1061            StakeAuthorize::Withdrawer,
1062            Some(&custodian_pubkey),
1063        );
1064        let mut message = Message::new(&[instruction], None);
1065        assert_eq!(
1066            parse_stake(
1067                &message.instructions[0],
1068                &AccountKeys::new(&message.account_keys, None)
1069            )
1070            .unwrap(),
1071            ParsedInstructionEnum {
1072                instruction_type: "authorizeChecked".to_string(),
1073                info: json!({
1074                    "stakeAccount": stake_pubkey.to_string(),
1075                    "clockSysvar": sysvar::clock::ID.to_string(),
1076                    "authority": authorized_pubkey.to_string(),
1077                    "newAuthority": new_authorized_pubkey.to_string(),
1078                    "authorityType": StakeAuthorize::Withdrawer,
1079                    "custodian": custodian_pubkey.to_string(),
1080                }),
1081            }
1082        );
1083        assert!(parse_stake(
1084            &message.instructions[0],
1085            &AccountKeys::new(&message.account_keys[0..4], None)
1086        )
1087        .is_err());
1088        let keys = message.account_keys.clone();
1089        message.instructions[0].accounts.pop();
1090        message.instructions[0].accounts.pop();
1091        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
1092    }
1093
1094    #[test]
1095    fn test_parse_stake_authorize_checked_with_seed_ix() {
1096        let stake_pubkey = Pubkey::new_unique();
1097        let authority_base_pubkey = Pubkey::new_unique();
1098        let authority_owner_pubkey = Pubkey::new_unique();
1099        let new_authorized_pubkey = Pubkey::new_unique();
1100        let custodian_pubkey = Pubkey::new_unique();
1101
1102        let seed = "test_seed";
1103        let instruction = instruction::authorize_checked_with_seed(
1104            &stake_pubkey,
1105            &authority_base_pubkey,
1106            seed.to_string(),
1107            &authority_owner_pubkey,
1108            &new_authorized_pubkey,
1109            StakeAuthorize::Staker,
1110            None,
1111        );
1112        let mut message = Message::new(&[instruction], None);
1113        assert_eq!(
1114            parse_stake(
1115                &message.instructions[0],
1116                &AccountKeys::new(&message.account_keys, None)
1117            )
1118            .unwrap(),
1119            ParsedInstructionEnum {
1120                instruction_type: "authorizeCheckedWithSeed".to_string(),
1121                info: json!({
1122                    "stakeAccount": stake_pubkey.to_string(),
1123                    "authorityOwner": authority_owner_pubkey.to_string(),
1124                    "newAuthorized": new_authorized_pubkey.to_string(),
1125                    "authorityBase": authority_base_pubkey.to_string(),
1126                    "authoritySeed": seed,
1127                    "authorityType": StakeAuthorize::Staker,
1128                    "clockSysvar": sysvar::clock::ID.to_string(),
1129                }),
1130            }
1131        );
1132        assert!(parse_stake(
1133            &message.instructions[0],
1134            &AccountKeys::new(&message.account_keys[0..3], None)
1135        )
1136        .is_err());
1137        let keys = message.account_keys.clone();
1138        message.instructions[0].accounts.pop();
1139        message.instructions[0].accounts.pop();
1140        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
1141
1142        let instruction = instruction::authorize_checked_with_seed(
1143            &stake_pubkey,
1144            &authority_base_pubkey,
1145            seed.to_string(),
1146            &authority_owner_pubkey,
1147            &new_authorized_pubkey,
1148            StakeAuthorize::Withdrawer,
1149            Some(&custodian_pubkey),
1150        );
1151        let mut message = Message::new(&[instruction], None);
1152        assert_eq!(
1153            parse_stake(
1154                &message.instructions[0],
1155                &AccountKeys::new(&message.account_keys, None)
1156            )
1157            .unwrap(),
1158            ParsedInstructionEnum {
1159                instruction_type: "authorizeCheckedWithSeed".to_string(),
1160                info: json!({
1161                    "stakeAccount": stake_pubkey.to_string(),
1162                    "authorityOwner": authority_owner_pubkey.to_string(),
1163                    "newAuthorized": new_authorized_pubkey.to_string(),
1164                    "authorityBase": authority_base_pubkey.to_string(),
1165                    "authoritySeed": seed,
1166                    "authorityType": StakeAuthorize::Withdrawer,
1167                    "clockSysvar": sysvar::clock::ID.to_string(),
1168                    "custodian": custodian_pubkey.to_string(),
1169                }),
1170            }
1171        );
1172        assert!(parse_stake(
1173            &message.instructions[0],
1174            &AccountKeys::new(&message.account_keys[0..4], None)
1175        )
1176        .is_err());
1177        let keys = message.account_keys.clone();
1178        message.instructions[0].accounts.pop();
1179        message.instructions[0].accounts.pop();
1180        assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
1181    }
1182
1183    #[test]
1184    fn test_parse_stake_move_ix() {
1185        let source_stake_pubkey = Pubkey::new_unique();
1186        let destination_stake_pubkey = Pubkey::new_unique();
1187        let authorized_pubkey = Pubkey::new_unique();
1188        let lamports = 1_000_000;
1189
1190        type InstructionFn = fn(&Pubkey, &Pubkey, &Pubkey, u64) -> Instruction;
1191        let test_vectors: Vec<(InstructionFn, String)> = vec![
1192            (instruction::move_stake, "moveStake".to_string()),
1193            (instruction::move_lamports, "moveLamports".to_string()),
1194        ];
1195
1196        for (mk_ixn, ixn_string) in test_vectors {
1197            let instruction = mk_ixn(
1198                &source_stake_pubkey,
1199                &destination_stake_pubkey,
1200                &authorized_pubkey,
1201                lamports,
1202            );
1203            let mut message = Message::new(&[instruction], None);
1204            assert_eq!(
1205                parse_stake(
1206                    &message.instructions[0],
1207                    &AccountKeys::new(&message.account_keys, None)
1208                )
1209                .unwrap(),
1210                ParsedInstructionEnum {
1211                    instruction_type: ixn_string,
1212                    info: json!({
1213                        "source": source_stake_pubkey.to_string(),
1214                        "destination": destination_stake_pubkey.to_string(),
1215                        "stakeAuthority": authorized_pubkey.to_string(),
1216                        "lamports": lamports,
1217                    }),
1218                }
1219            );
1220            assert!(parse_stake(
1221                &message.instructions[0],
1222                &AccountKeys::new(&message.account_keys[0..2], None)
1223            )
1224            .is_err());
1225            let keys = message.account_keys.clone();
1226            message.instructions[0].accounts.pop();
1227            assert!(parse_stake(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
1228        }
1229    }
1230}