|
50 | 50 |
|
51 | 51 | // NOTE ideas for future tests: |
52 | 52 | // * fail with different vote accounts on operations that require them to match |
53 | | -// * fail with different authorities on operations that require them to match |
54 | | -// * adding/changing lockups to ensure we always fail when violating lockup |
| 53 | +// * fail with different authorities/lockups on operations that require metas to match |
55 | 54 |
|
56 | 55 | // arbitrary, gives us room to set up activations/deactivations |
57 | 56 | const EXECUTION_EPOCH: u64 = 8; |
@@ -360,6 +359,32 @@ impl StakeInterface { |
360 | 359 | 128 |
361 | 360 | } |
362 | 361 |
|
| 362 | + // state of any existing lockup on the stake accounts |
| 363 | + fn lockup_state(self) -> LockupState { |
| 364 | + match self { |
| 365 | + Self::Initialize { .. } |
| 366 | + | Self::InitializeChecked |
| 367 | + | Self::Withdraw { |
| 368 | + source_status: WithdrawStatus::Uninitialized, |
| 369 | + .. |
| 370 | + } => LockupState::None, |
| 371 | + Self::Authorize { lockup_state, .. } |
| 372 | + | Self::AuthorizeWithSeed { lockup_state, .. } |
| 373 | + | Self::SetLockup { |
| 374 | + existing_lockup_state: lockup_state, |
| 375 | + .. |
| 376 | + } |
| 377 | + | Self::DelegateStake { lockup_state, .. } |
| 378 | + | Self::Split { lockup_state, .. } |
| 379 | + | Self::Merge { lockup_state, .. } |
| 380 | + | Self::MoveStake { lockup_state, .. } |
| 381 | + | Self::MoveLamports { lockup_state, .. } |
| 382 | + | Self::Withdraw { lockup_state, .. } |
| 383 | + | Self::Deactivate { lockup_state, .. } |
| 384 | + | Self::DeactivateDelinquent { lockup_state, .. } => lockup_state, |
| 385 | + } |
| 386 | + } |
| 387 | + |
363 | 388 | // creates an instruction with the given combination of settings that is guaranteed to succeed |
364 | 389 | fn to_instruction(self, env: &mut Env) -> Instruction { |
365 | 390 | let minimum_delegation = get_minimum_delegation(); |
@@ -975,6 +1000,68 @@ fn test_no_signer_bypass() { |
975 | 1000 | } |
976 | 1001 | } |
977 | 1002 |
|
| 1003 | +// operations that require a custodian fail without it |
| 1004 | +#[test] |
| 1005 | +fn test_no_custodian_bypass() { |
| 1006 | + let mut env = Env::init(); |
| 1007 | + |
| 1008 | + for declaration in &*INSTRUCTION_DECLARATIONS { |
| 1009 | + // skip if no lockup |
| 1010 | + if declaration.lockup_state() != LockupState::Active { |
| 1011 | + continue; |
| 1012 | + } |
| 1013 | + |
| 1014 | + // changing staker does not require custodian |
| 1015 | + match declaration { |
| 1016 | + StakeInterface::Authorize { |
| 1017 | + authority_type: AuthorityType::Staker, |
| 1018 | + .. |
| 1019 | + } |
| 1020 | + | StakeInterface::AuthorizeWithSeed { |
| 1021 | + authority_type: AuthorityType::Staker, |
| 1022 | + .. |
| 1023 | + } => { |
| 1024 | + continue; |
| 1025 | + } |
| 1026 | + _ => (), |
| 1027 | + } |
| 1028 | + |
| 1029 | + let mut instruction = declaration.to_instruction(&mut env); |
| 1030 | + |
| 1031 | + // skip if successful instruction requires no custodian |
| 1032 | + if !instruction |
| 1033 | + .accounts |
| 1034 | + .iter() |
| 1035 | + .any(|account| account.pubkey == CUSTODIAN_LEFT) |
| 1036 | + { |
| 1037 | + continue; |
| 1038 | + } |
| 1039 | + |
| 1040 | + // remove the custodian |
| 1041 | + // active custodian is always Left. we only use Right as a new lockup target |
| 1042 | + // the only instruction that takes Right as account data is SetLockupChecked |
| 1043 | + // and we must remove it too because it comes after Left in the accounts list |
| 1044 | + instruction.accounts.retain(|account| { |
| 1045 | + account.pubkey != CUSTODIAN_LEFT && account.pubkey != CUSTODIAN_RIGHT |
| 1046 | + }); |
| 1047 | + |
| 1048 | + env.process_fail(&instruction); |
| 1049 | + env.reset(); |
| 1050 | + |
| 1051 | + let mut instruction = declaration.to_instruction(&mut env); |
| 1052 | + |
| 1053 | + // change to the wrong custodian |
| 1054 | + instruction.accounts.iter_mut().for_each(|account| { |
| 1055 | + if account.pubkey == CUSTODIAN_LEFT { |
| 1056 | + account.pubkey = CUSTODIAN_RIGHT |
| 1057 | + } |
| 1058 | + }); |
| 1059 | + |
| 1060 | + env.process_fail(&instruction); |
| 1061 | + env.reset(); |
| 1062 | + } |
| 1063 | +} |
| 1064 | + |
978 | 1065 | // the stake program cannot be used during the epoch rewards period |
979 | 1066 | // the only exception to this is GetMinimumDelegation |
980 | 1067 | #[test] |
|
0 commit comments