solana_transaction_status_wasm/
parse_stake.rs

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