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