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 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}