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