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