Removes KeyedAccount from tests in stake instruction. (Part 4) (#24124)
* Moves tests from stake state to stake instruction. * Migrates test_merge. * Migrates test_merge_self_fails. * Migrates test_merge_incorrect_authorized_staker. * Migrates test_merge_invalid_account_data. * Migrates test_merge_fake_stake_source. * Migrates test_merge_active_stake.
This commit is contained in:
parent
24cc6c33de
commit
07f4a9040a
|
@ -359,8 +359,8 @@ mod tests {
|
|||
instruction::{self, LockupArgs},
|
||||
state::{Authorized, Lockup, StakeAuthorize},
|
||||
},
|
||||
system_program,
|
||||
sysvar::{self, stake_history::StakeHistory},
|
||||
stake_history::{StakeHistory, StakeHistoryEntry},
|
||||
system_program, sysvar,
|
||||
},
|
||||
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
|
||||
std::{collections::HashSet, str::FromStr},
|
||||
|
@ -5176,4 +5176,694 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge() {
|
||||
let stake_address = solana_sdk::pubkey::new_rand();
|
||||
let merge_from_address = solana_sdk::pubkey::new_rand();
|
||||
let authorized_address = solana_sdk::pubkey::new_rand();
|
||||
let meta = Meta::auto(&authorized_address);
|
||||
let stake_lamports = 42;
|
||||
let mut instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: stake_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: merge_from_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::stake_history::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: authorized_address,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
|
||||
for state in &[
|
||||
StakeState::Initialized(meta),
|
||||
just_stake(meta, stake_lamports),
|
||||
] {
|
||||
let stake_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
for merge_from_state in &[
|
||||
StakeState::Initialized(meta),
|
||||
just_stake(meta, stake_lamports),
|
||||
] {
|
||||
let merge_from_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
merge_from_state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let transaction_accounts = vec![
|
||||
(stake_address, stake_account.clone()),
|
||||
(merge_from_address, merge_from_account),
|
||||
(authorized_address, AccountSharedData::default()),
|
||||
(
|
||||
sysvar::clock::id(),
|
||||
account::create_account_shared_data_for_test(&Clock::default()),
|
||||
),
|
||||
(
|
||||
sysvar::stake_history::id(),
|
||||
account::create_account_shared_data_for_test(&StakeHistory::default()),
|
||||
),
|
||||
];
|
||||
|
||||
// Authorized staker signature required...
|
||||
instruction_accounts[4].is_signer = false;
|
||||
process_instruction(
|
||||
&serialize(&StakeInstruction::Merge).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
instruction_accounts[4].is_signer = true;
|
||||
|
||||
let accounts = process_instruction(
|
||||
&serialize(&StakeInstruction::Merge).unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts.clone(),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// check lamports
|
||||
assert_eq!(accounts[0].lamports(), stake_lamports * 2);
|
||||
assert_eq!(accounts[1].lamports(), 0);
|
||||
|
||||
// check state
|
||||
match state {
|
||||
StakeState::Initialized(meta) => {
|
||||
assert_eq!(accounts[0].state(), Ok(StakeState::Initialized(*meta)),);
|
||||
}
|
||||
StakeState::Stake(meta, stake) => {
|
||||
let expected_stake = stake.delegation.stake
|
||||
+ merge_from_state
|
||||
.stake()
|
||||
.map(|stake| stake.delegation.stake)
|
||||
.unwrap_or_else(|| {
|
||||
stake_lamports
|
||||
- merge_from_state.meta().unwrap().rent_exempt_reserve
|
||||
});
|
||||
assert_eq!(
|
||||
accounts[0].state(),
|
||||
Ok(StakeState::Stake(
|
||||
*meta,
|
||||
Stake {
|
||||
delegation: Delegation {
|
||||
stake: expected_stake,
|
||||
..stake.delegation
|
||||
},
|
||||
..*stake
|
||||
}
|
||||
)),
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
assert_eq!(accounts[1].state(), Ok(StakeState::Uninitialized));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_self_fails() {
|
||||
let stake_address = solana_sdk::pubkey::new_rand();
|
||||
let authorized_address = solana_sdk::pubkey::new_rand();
|
||||
let rent = Rent::default();
|
||||
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
||||
let stake_amount = 4242424242;
|
||||
let stake_lamports = rent_exempt_reserve + stake_amount;
|
||||
let meta = Meta {
|
||||
rent_exempt_reserve,
|
||||
..Meta::auto(&authorized_address)
|
||||
};
|
||||
let stake = Stake {
|
||||
delegation: Delegation {
|
||||
stake: stake_amount,
|
||||
activation_epoch: 0,
|
||||
..Delegation::default()
|
||||
},
|
||||
..Stake::default()
|
||||
};
|
||||
let stake_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Stake(meta, stake),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let transaction_accounts = vec![
|
||||
(stake_address, stake_account),
|
||||
(authorized_address, AccountSharedData::default()),
|
||||
(
|
||||
sysvar::clock::id(),
|
||||
account::create_account_shared_data_for_test(&Clock::default()),
|
||||
),
|
||||
(
|
||||
sysvar::stake_history::id(),
|
||||
account::create_account_shared_data_for_test(&StakeHistory::default()),
|
||||
),
|
||||
];
|
||||
let instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: stake_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: stake_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::stake_history::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: authorized_address,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
|
||||
process_instruction(
|
||||
&serialize(&StakeInstruction::Merge).unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts,
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_incorrect_authorized_staker() {
|
||||
let stake_address = solana_sdk::pubkey::new_rand();
|
||||
let merge_from_address = solana_sdk::pubkey::new_rand();
|
||||
let authorized_address = solana_sdk::pubkey::new_rand();
|
||||
let wrong_authorized_address = solana_sdk::pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let mut instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: stake_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: merge_from_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::stake_history::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: authorized_address,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
|
||||
for state in &[
|
||||
StakeState::Initialized(Meta::auto(&authorized_address)),
|
||||
just_stake(Meta::auto(&authorized_address), stake_lamports),
|
||||
] {
|
||||
let stake_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
for merge_from_state in &[
|
||||
StakeState::Initialized(Meta::auto(&wrong_authorized_address)),
|
||||
just_stake(Meta::auto(&wrong_authorized_address), stake_lamports),
|
||||
] {
|
||||
let merge_from_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
merge_from_state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let transaction_accounts = vec![
|
||||
(stake_address, stake_account.clone()),
|
||||
(merge_from_address, merge_from_account),
|
||||
(authorized_address, AccountSharedData::default()),
|
||||
(wrong_authorized_address, AccountSharedData::default()),
|
||||
(
|
||||
sysvar::clock::id(),
|
||||
account::create_account_shared_data_for_test(&Clock::default()),
|
||||
),
|
||||
(
|
||||
sysvar::stake_history::id(),
|
||||
account::create_account_shared_data_for_test(&StakeHistory::default()),
|
||||
),
|
||||
];
|
||||
|
||||
instruction_accounts[4].pubkey = wrong_authorized_address;
|
||||
process_instruction(
|
||||
&serialize(&StakeInstruction::Merge).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
instruction_accounts[4].pubkey = authorized_address;
|
||||
|
||||
process_instruction(
|
||||
&serialize(&StakeInstruction::Merge).unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts.clone(),
|
||||
Err(StakeError::MergeMismatch.into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_invalid_account_data() {
|
||||
let stake_address = solana_sdk::pubkey::new_rand();
|
||||
let merge_from_address = solana_sdk::pubkey::new_rand();
|
||||
let authorized_address = solana_sdk::pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: stake_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: merge_from_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::stake_history::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: authorized_address,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
|
||||
for state in &[
|
||||
StakeState::Uninitialized,
|
||||
StakeState::RewardsPool,
|
||||
StakeState::Initialized(Meta::auto(&authorized_address)),
|
||||
just_stake(Meta::auto(&authorized_address), stake_lamports),
|
||||
] {
|
||||
let stake_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
for merge_from_state in &[StakeState::Uninitialized, StakeState::RewardsPool] {
|
||||
let merge_from_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
merge_from_state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let transaction_accounts = vec![
|
||||
(stake_address, stake_account.clone()),
|
||||
(merge_from_address, merge_from_account),
|
||||
(authorized_address, AccountSharedData::default()),
|
||||
(
|
||||
sysvar::clock::id(),
|
||||
account::create_account_shared_data_for_test(&Clock::default()),
|
||||
),
|
||||
(
|
||||
sysvar::stake_history::id(),
|
||||
account::create_account_shared_data_for_test(&StakeHistory::default()),
|
||||
),
|
||||
];
|
||||
|
||||
process_instruction(
|
||||
&serialize(&StakeInstruction::Merge).unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_fake_stake_source() {
|
||||
let stake_address = solana_sdk::pubkey::new_rand();
|
||||
let merge_from_address = solana_sdk::pubkey::new_rand();
|
||||
let authorized_address = solana_sdk::pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let stake_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
&just_stake(Meta::auto(&authorized_address), stake_lamports),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let merge_from_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
&just_stake(Meta::auto(&authorized_address), stake_lamports),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&solana_sdk::pubkey::new_rand(),
|
||||
)
|
||||
.unwrap();
|
||||
let transaction_accounts = vec![
|
||||
(stake_address, stake_account),
|
||||
(merge_from_address, merge_from_account),
|
||||
(authorized_address, AccountSharedData::default()),
|
||||
(
|
||||
sysvar::clock::id(),
|
||||
account::create_account_shared_data_for_test(&Clock::default()),
|
||||
),
|
||||
(
|
||||
sysvar::stake_history::id(),
|
||||
account::create_account_shared_data_for_test(&StakeHistory::default()),
|
||||
),
|
||||
];
|
||||
let instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: stake_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: merge_from_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::stake_history::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: authorized_address,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
|
||||
process_instruction(
|
||||
&serialize(&StakeInstruction::Merge).unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts,
|
||||
Err(InstructionError::IncorrectProgramId),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_active_stake() {
|
||||
let stake_address = solana_sdk::pubkey::new_rand();
|
||||
let merge_from_address = solana_sdk::pubkey::new_rand();
|
||||
let authorized_address = solana_sdk::pubkey::new_rand();
|
||||
let base_lamports = 4242424242;
|
||||
let rent = Rent::default();
|
||||
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
||||
let stake_amount = base_lamports;
|
||||
let stake_lamports = rent_exempt_reserve + stake_amount;
|
||||
let merge_from_amount = base_lamports;
|
||||
let merge_from_lamports = rent_exempt_reserve + merge_from_amount;
|
||||
let meta = Meta {
|
||||
rent_exempt_reserve,
|
||||
..Meta::auto(&authorized_address)
|
||||
};
|
||||
let mut stake = Stake {
|
||||
delegation: Delegation {
|
||||
stake: stake_amount,
|
||||
activation_epoch: 0,
|
||||
..Delegation::default()
|
||||
},
|
||||
..Stake::default()
|
||||
};
|
||||
let stake_account = AccountSharedData::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Stake(meta, stake),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let merge_from_activation_epoch = 2;
|
||||
let mut merge_from_stake = Stake {
|
||||
delegation: Delegation {
|
||||
stake: merge_from_amount,
|
||||
activation_epoch: merge_from_activation_epoch,
|
||||
..stake.delegation
|
||||
},
|
||||
..stake
|
||||
};
|
||||
let merge_from_account = AccountSharedData::new_data_with_space(
|
||||
merge_from_lamports,
|
||||
&StakeState::Stake(meta, merge_from_stake),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut clock = Clock::default();
|
||||
let mut stake_history = StakeHistory::default();
|
||||
let mut effective = base_lamports;
|
||||
let mut activating = stake_amount;
|
||||
let mut deactivating = 0;
|
||||
stake_history.add(
|
||||
clock.epoch,
|
||||
StakeHistoryEntry {
|
||||
effective,
|
||||
activating,
|
||||
deactivating,
|
||||
},
|
||||
);
|
||||
let mut transaction_accounts = vec![
|
||||
(stake_address, stake_account),
|
||||
(merge_from_address, merge_from_account),
|
||||
(authorized_address, AccountSharedData::default()),
|
||||
(
|
||||
sysvar::clock::id(),
|
||||
account::create_account_shared_data_for_test(&clock),
|
||||
),
|
||||
(
|
||||
sysvar::stake_history::id(),
|
||||
account::create_account_shared_data_for_test(&stake_history),
|
||||
),
|
||||
];
|
||||
let instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: stake_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: merge_from_address,
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::stake_history::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: authorized_address,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
|
||||
fn try_merge(
|
||||
transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
|
||||
mut instruction_accounts: Vec<AccountMeta>,
|
||||
expected_result: Result<(), InstructionError>,
|
||||
) {
|
||||
for iteration in 0..2 {
|
||||
if iteration == 1 {
|
||||
instruction_accounts.swap(0, 1);
|
||||
}
|
||||
let accounts = process_instruction_with_sysvar_cache(
|
||||
&serialize(&StakeInstruction::Merge).unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
None,
|
||||
expected_result.clone(),
|
||||
);
|
||||
if expected_result.is_ok() {
|
||||
assert_eq!(
|
||||
accounts[1 - iteration].state(),
|
||||
Ok(StakeState::Uninitialized)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stake activation epoch, source initialized succeeds
|
||||
try_merge(
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// both activating fails
|
||||
loop {
|
||||
clock.epoch += 1;
|
||||
if clock.epoch == merge_from_activation_epoch {
|
||||
activating += merge_from_amount;
|
||||
}
|
||||
let delta =
|
||||
activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
|
||||
effective += delta;
|
||||
activating -= delta;
|
||||
stake_history.add(
|
||||
clock.epoch,
|
||||
StakeHistoryEntry {
|
||||
effective,
|
||||
activating,
|
||||
deactivating,
|
||||
},
|
||||
);
|
||||
transaction_accounts[3] = (
|
||||
sysvar::clock::id(),
|
||||
account::create_account_shared_data_for_test(&clock),
|
||||
);
|
||||
transaction_accounts[4] = (
|
||||
sysvar::stake_history::id(),
|
||||
account::create_account_shared_data_for_test(&stake_history),
|
||||
);
|
||||
if stake_amount == stake.stake(clock.epoch, Some(&stake_history))
|
||||
&& merge_from_amount == merge_from_stake.stake(clock.epoch, Some(&stake_history))
|
||||
{
|
||||
break;
|
||||
}
|
||||
try_merge(
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::from(StakeError::MergeTransientStake)),
|
||||
);
|
||||
}
|
||||
|
||||
// Both fully activated works
|
||||
try_merge(
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// deactivate setup for deactivation
|
||||
let merge_from_deactivation_epoch = clock.epoch + 1;
|
||||
let stake_deactivation_epoch = clock.epoch + 2;
|
||||
|
||||
// active/deactivating and deactivating/inactive mismatches fail
|
||||
loop {
|
||||
clock.epoch += 1;
|
||||
let delta =
|
||||
deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
|
||||
effective -= delta;
|
||||
deactivating -= delta;
|
||||
if clock.epoch == stake_deactivation_epoch {
|
||||
deactivating += stake_amount;
|
||||
stake = Stake {
|
||||
delegation: Delegation {
|
||||
deactivation_epoch: stake_deactivation_epoch,
|
||||
..stake.delegation
|
||||
},
|
||||
..stake
|
||||
};
|
||||
transaction_accounts[0]
|
||||
.1
|
||||
.set_state(&StakeState::Stake(meta, stake))
|
||||
.unwrap();
|
||||
}
|
||||
if clock.epoch == merge_from_deactivation_epoch {
|
||||
deactivating += merge_from_amount;
|
||||
merge_from_stake = Stake {
|
||||
delegation: Delegation {
|
||||
deactivation_epoch: merge_from_deactivation_epoch,
|
||||
..merge_from_stake.delegation
|
||||
},
|
||||
..merge_from_stake
|
||||
};
|
||||
transaction_accounts[1]
|
||||
.1
|
||||
.set_state(&StakeState::Stake(meta, merge_from_stake))
|
||||
.unwrap();
|
||||
}
|
||||
stake_history.add(
|
||||
clock.epoch,
|
||||
StakeHistoryEntry {
|
||||
effective,
|
||||
activating,
|
||||
deactivating,
|
||||
},
|
||||
);
|
||||
transaction_accounts[3] = (
|
||||
sysvar::clock::id(),
|
||||
account::create_account_shared_data_for_test(&clock),
|
||||
);
|
||||
transaction_accounts[4] = (
|
||||
sysvar::stake_history::id(),
|
||||
account::create_account_shared_data_for_test(&stake_history),
|
||||
);
|
||||
if 0 == stake.stake(clock.epoch, Some(&stake_history))
|
||||
&& 0 == merge_from_stake.stake(clock.epoch, Some(&stake_history))
|
||||
{
|
||||
break;
|
||||
}
|
||||
try_merge(
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::from(StakeError::MergeTransientStake)),
|
||||
);
|
||||
}
|
||||
|
||||
// Both fully deactivated works
|
||||
try_merge(transaction_accounts, instruction_accounts, Ok(()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1450,7 +1450,6 @@ mod tests {
|
|||
sysvar::SysvarId,
|
||||
transaction_context::TransactionContext,
|
||||
},
|
||||
std::iter::FromIterator,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
@ -2529,596 +2528,6 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
fn just_stake(stake: u64) -> Stake {
|
||||
Stake {
|
||||
delegation: Delegation {
|
||||
stake,
|
||||
..Delegation::default()
|
||||
},
|
||||
..Stake::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge() {
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let source_stake_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let authorized_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
|
||||
let signers = vec![authorized_pubkey].into_iter().collect();
|
||||
|
||||
for state in &[
|
||||
StakeState::Initialized(Meta::auto(&authorized_pubkey)),
|
||||
StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
||||
] {
|
||||
for source_state in &[
|
||||
StakeState::Initialized(Meta::auto(&authorized_pubkey)),
|
||||
StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
||||
] {
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||
|
||||
let source_stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
source_state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("source_stake_account");
|
||||
let source_stake_keyed_account =
|
||||
KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account);
|
||||
|
||||
// Authorized staker signature required...
|
||||
assert_eq!(
|
||||
stake_keyed_account.merge(
|
||||
&invoke_context,
|
||||
&source_stake_keyed_account,
|
||||
&Clock::default(),
|
||||
&StakeHistory::default(),
|
||||
&HashSet::new(),
|
||||
),
|
||||
Err(InstructionError::MissingRequiredSignature)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
stake_keyed_account.merge(
|
||||
&invoke_context,
|
||||
&source_stake_keyed_account,
|
||||
&Clock::default(),
|
||||
&StakeHistory::default(),
|
||||
&signers,
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
// check lamports
|
||||
assert_eq!(
|
||||
stake_keyed_account.account.borrow().lamports(),
|
||||
stake_lamports * 2
|
||||
);
|
||||
assert_eq!(source_stake_keyed_account.account.borrow().lamports(), 0);
|
||||
|
||||
// check state
|
||||
match state {
|
||||
StakeState::Initialized(meta) => {
|
||||
assert_eq!(
|
||||
stake_keyed_account.state(),
|
||||
Ok(StakeState::Initialized(*meta)),
|
||||
);
|
||||
}
|
||||
StakeState::Stake(meta, stake) => {
|
||||
let expected_stake = stake.delegation.stake
|
||||
+ source_state
|
||||
.stake()
|
||||
.map(|stake| stake.delegation.stake)
|
||||
.unwrap_or_else(|| {
|
||||
stake_lamports
|
||||
- source_state.meta().unwrap().rent_exempt_reserve
|
||||
});
|
||||
assert_eq!(
|
||||
stake_keyed_account.state(),
|
||||
Ok(StakeState::Stake(
|
||||
*meta,
|
||||
Stake {
|
||||
delegation: Delegation {
|
||||
stake: expected_stake,
|
||||
..stake.delegation
|
||||
},
|
||||
..*stake
|
||||
}
|
||||
)),
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
assert_eq!(
|
||||
source_stake_keyed_account.state(),
|
||||
Ok(StakeState::Uninitialized)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_self_fails() {
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||
let stake_address = Pubkey::new_unique();
|
||||
let authority_pubkey = Pubkey::new_unique();
|
||||
let signers = HashSet::from_iter(vec![authority_pubkey]);
|
||||
let rent = Rent::default();
|
||||
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
||||
let stake_amount = 4242424242;
|
||||
let stake_lamports = rent_exempt_reserve + stake_amount;
|
||||
|
||||
let meta = Meta {
|
||||
rent_exempt_reserve,
|
||||
..Meta::auto(&authority_pubkey)
|
||||
};
|
||||
let stake = Stake {
|
||||
delegation: Delegation {
|
||||
stake: stake_amount,
|
||||
activation_epoch: 0,
|
||||
..Delegation::default()
|
||||
},
|
||||
..Stake::default()
|
||||
};
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Stake(meta, stake),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
let stake_keyed_account = KeyedAccount::new(&stake_address, true, &stake_account);
|
||||
|
||||
assert_eq!(
|
||||
stake_keyed_account.merge(
|
||||
&invoke_context,
|
||||
&stake_keyed_account,
|
||||
&Clock::default(),
|
||||
&StakeHistory::default(),
|
||||
&signers,
|
||||
),
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_incorrect_authorized_staker() {
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let source_stake_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let authorized_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let wrong_authorized_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
|
||||
let signers = vec![authorized_pubkey].into_iter().collect();
|
||||
let wrong_signers = vec![wrong_authorized_pubkey].into_iter().collect();
|
||||
|
||||
for state in &[
|
||||
StakeState::Initialized(Meta::auto(&authorized_pubkey)),
|
||||
StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
||||
] {
|
||||
for source_state in &[
|
||||
StakeState::Initialized(Meta::auto(&wrong_authorized_pubkey)),
|
||||
StakeState::Stake(
|
||||
Meta::auto(&wrong_authorized_pubkey),
|
||||
just_stake(stake_lamports),
|
||||
),
|
||||
] {
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||
|
||||
let source_stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
source_state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("source_stake_account");
|
||||
let source_stake_keyed_account =
|
||||
KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account);
|
||||
|
||||
assert_eq!(
|
||||
stake_keyed_account.merge(
|
||||
&invoke_context,
|
||||
&source_stake_keyed_account,
|
||||
&Clock::default(),
|
||||
&StakeHistory::default(),
|
||||
&wrong_signers,
|
||||
),
|
||||
Err(InstructionError::MissingRequiredSignature)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
stake_keyed_account.merge(
|
||||
&invoke_context,
|
||||
&source_stake_keyed_account,
|
||||
&Clock::default(),
|
||||
&StakeHistory::default(),
|
||||
&signers,
|
||||
),
|
||||
Err(StakeError::MergeMismatch.into())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_invalid_account_data() {
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let source_stake_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let authorized_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let signers = vec![authorized_pubkey].into_iter().collect();
|
||||
|
||||
for state in &[
|
||||
StakeState::Uninitialized,
|
||||
StakeState::RewardsPool,
|
||||
StakeState::Initialized(Meta::auto(&authorized_pubkey)),
|
||||
StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
||||
] {
|
||||
for source_state in &[StakeState::Uninitialized, StakeState::RewardsPool] {
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||
|
||||
let source_stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
source_state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("source_stake_account");
|
||||
let source_stake_keyed_account =
|
||||
KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account);
|
||||
|
||||
assert_eq!(
|
||||
stake_keyed_account.merge(
|
||||
&invoke_context,
|
||||
&source_stake_keyed_account,
|
||||
&Clock::default(),
|
||||
&StakeHistory::default(),
|
||||
&signers,
|
||||
),
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_fake_stake_source() {
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||
let stake_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let source_stake_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let authorized_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
|
||||
let signers = vec![authorized_pubkey].into_iter().collect();
|
||||
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
let stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &stake_account);
|
||||
|
||||
let source_stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&solana_sdk::pubkey::new_rand(),
|
||||
)
|
||||
.expect("source_stake_account");
|
||||
let source_stake_keyed_account =
|
||||
KeyedAccount::new(&source_stake_pubkey, true, &source_stake_account);
|
||||
|
||||
assert_eq!(
|
||||
stake_keyed_account.merge(
|
||||
&invoke_context,
|
||||
&source_stake_keyed_account,
|
||||
&Clock::default(),
|
||||
&StakeHistory::default(),
|
||||
&signers,
|
||||
),
|
||||
Err(InstructionError::IncorrectProgramId)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_active_stake() {
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
||||
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||
let base_lamports = 4242424242;
|
||||
let stake_address = Pubkey::new_unique();
|
||||
let source_address = Pubkey::new_unique();
|
||||
let authority_pubkey = Pubkey::new_unique();
|
||||
let signers = HashSet::from_iter(vec![authority_pubkey]);
|
||||
let rent = Rent::default();
|
||||
let rent_exempt_reserve = rent.minimum_balance(std::mem::size_of::<StakeState>());
|
||||
let stake_amount = base_lamports;
|
||||
let stake_lamports = rent_exempt_reserve + stake_amount;
|
||||
let source_amount = base_lamports;
|
||||
let source_lamports = rent_exempt_reserve + source_amount;
|
||||
|
||||
let meta = Meta {
|
||||
rent_exempt_reserve,
|
||||
..Meta::auto(&authority_pubkey)
|
||||
};
|
||||
let mut stake = Stake {
|
||||
delegation: Delegation {
|
||||
stake: stake_amount,
|
||||
activation_epoch: 0,
|
||||
..Delegation::default()
|
||||
},
|
||||
..Stake::default()
|
||||
};
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Stake(meta, stake),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
let stake_keyed_account = KeyedAccount::new(&stake_address, true, &stake_account);
|
||||
|
||||
let source_activation_epoch = 2;
|
||||
let mut source_stake = Stake {
|
||||
delegation: Delegation {
|
||||
stake: source_amount,
|
||||
activation_epoch: source_activation_epoch,
|
||||
..stake.delegation
|
||||
},
|
||||
..stake
|
||||
};
|
||||
let source_account = AccountSharedData::new_ref_data_with_space(
|
||||
source_lamports,
|
||||
&StakeState::Stake(meta, source_stake),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("source_account");
|
||||
let source_keyed_account = KeyedAccount::new(&source_address, true, &source_account);
|
||||
|
||||
let mut clock = Clock::default();
|
||||
let mut stake_history = StakeHistory::default();
|
||||
|
||||
clock.epoch = 0;
|
||||
let mut effective = base_lamports;
|
||||
let mut activating = stake_amount;
|
||||
let mut deactivating = 0;
|
||||
stake_history.add(
|
||||
clock.epoch,
|
||||
StakeHistoryEntry {
|
||||
effective,
|
||||
activating,
|
||||
deactivating,
|
||||
},
|
||||
);
|
||||
|
||||
fn try_merge(
|
||||
invoke_context: &InvokeContext,
|
||||
stake_account: &KeyedAccount,
|
||||
source_account: &KeyedAccount,
|
||||
clock: &Clock,
|
||||
stake_history: &StakeHistory,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let test_stake_account = stake_account.account.clone();
|
||||
let test_stake_keyed =
|
||||
KeyedAccount::new(stake_account.unsigned_key(), true, &test_stake_account);
|
||||
let test_source_account = source_account.account.clone();
|
||||
let test_source_keyed =
|
||||
KeyedAccount::new(source_account.unsigned_key(), true, &test_source_account);
|
||||
|
||||
let result = test_stake_keyed.merge(
|
||||
invoke_context,
|
||||
&test_source_keyed,
|
||||
clock,
|
||||
stake_history,
|
||||
signers,
|
||||
);
|
||||
if result.is_ok() {
|
||||
assert_eq!(test_source_keyed.state(), Ok(StakeState::Uninitialized),);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
// stake activation epoch, source initialized succeeds
|
||||
assert!(try_merge(
|
||||
&invoke_context,
|
||||
&stake_keyed_account,
|
||||
&source_keyed_account,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers
|
||||
)
|
||||
.is_ok(),);
|
||||
assert!(try_merge(
|
||||
&invoke_context,
|
||||
&source_keyed_account,
|
||||
&stake_keyed_account,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers
|
||||
)
|
||||
.is_ok(),);
|
||||
|
||||
// both activating fails
|
||||
loop {
|
||||
clock.epoch += 1;
|
||||
if clock.epoch == source_activation_epoch {
|
||||
activating += source_amount;
|
||||
}
|
||||
let delta =
|
||||
activating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
|
||||
effective += delta;
|
||||
activating -= delta;
|
||||
stake_history.add(
|
||||
clock.epoch,
|
||||
StakeHistoryEntry {
|
||||
effective,
|
||||
activating,
|
||||
deactivating,
|
||||
},
|
||||
);
|
||||
if stake_amount == stake.stake(clock.epoch, Some(&stake_history))
|
||||
&& source_amount == source_stake.stake(clock.epoch, Some(&stake_history))
|
||||
{
|
||||
break;
|
||||
}
|
||||
assert_eq!(
|
||||
try_merge(
|
||||
&invoke_context,
|
||||
&stake_keyed_account,
|
||||
&source_keyed_account,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers
|
||||
)
|
||||
.unwrap_err(),
|
||||
InstructionError::from(StakeError::MergeTransientStake),
|
||||
);
|
||||
assert_eq!(
|
||||
try_merge(
|
||||
&invoke_context,
|
||||
&source_keyed_account,
|
||||
&stake_keyed_account,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers
|
||||
)
|
||||
.unwrap_err(),
|
||||
InstructionError::from(StakeError::MergeTransientStake),
|
||||
);
|
||||
}
|
||||
// Both fully activated works
|
||||
assert!(try_merge(
|
||||
&invoke_context,
|
||||
&stake_keyed_account,
|
||||
&source_keyed_account,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers
|
||||
)
|
||||
.is_ok(),);
|
||||
|
||||
// deactivate setup for deactivation
|
||||
let source_deactivation_epoch = clock.epoch + 1;
|
||||
let stake_deactivation_epoch = clock.epoch + 2;
|
||||
|
||||
// active/deactivating and deactivating/inactive mismatches fail
|
||||
loop {
|
||||
clock.epoch += 1;
|
||||
let delta =
|
||||
deactivating.min((effective as f64 * stake.delegation.warmup_cooldown_rate) as u64);
|
||||
effective -= delta;
|
||||
deactivating -= delta;
|
||||
if clock.epoch == stake_deactivation_epoch {
|
||||
deactivating += stake_amount;
|
||||
stake = Stake {
|
||||
delegation: Delegation {
|
||||
deactivation_epoch: stake_deactivation_epoch,
|
||||
..stake.delegation
|
||||
},
|
||||
..stake
|
||||
};
|
||||
stake_keyed_account
|
||||
.set_state(&StakeState::Stake(meta, stake))
|
||||
.unwrap();
|
||||
}
|
||||
if clock.epoch == source_deactivation_epoch {
|
||||
deactivating += source_amount;
|
||||
source_stake = Stake {
|
||||
delegation: Delegation {
|
||||
deactivation_epoch: source_deactivation_epoch,
|
||||
..source_stake.delegation
|
||||
},
|
||||
..source_stake
|
||||
};
|
||||
source_keyed_account
|
||||
.set_state(&StakeState::Stake(meta, source_stake))
|
||||
.unwrap();
|
||||
}
|
||||
stake_history.add(
|
||||
clock.epoch,
|
||||
StakeHistoryEntry {
|
||||
effective,
|
||||
activating,
|
||||
deactivating,
|
||||
},
|
||||
);
|
||||
if 0 == stake.stake(clock.epoch, Some(&stake_history))
|
||||
&& 0 == source_stake.stake(clock.epoch, Some(&stake_history))
|
||||
{
|
||||
break;
|
||||
}
|
||||
assert_eq!(
|
||||
try_merge(
|
||||
&invoke_context,
|
||||
&stake_keyed_account,
|
||||
&source_keyed_account,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers
|
||||
)
|
||||
.unwrap_err(),
|
||||
InstructionError::from(StakeError::MergeTransientStake),
|
||||
);
|
||||
assert_eq!(
|
||||
try_merge(
|
||||
&invoke_context,
|
||||
&source_keyed_account,
|
||||
&stake_keyed_account,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers
|
||||
)
|
||||
.unwrap_err(),
|
||||
InstructionError::from(StakeError::MergeTransientStake),
|
||||
);
|
||||
}
|
||||
|
||||
// Both fully deactivated works
|
||||
assert!(try_merge(
|
||||
&invoke_context,
|
||||
&stake_keyed_account,
|
||||
&source_keyed_account,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers
|
||||
)
|
||||
.is_ok(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lockup_is_expired() {
|
||||
let custodian = solana_sdk::pubkey::new_rand();
|
||||
|
|
Loading…
Reference in New Issue