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:
Alexander Meißner 2022-04-06 12:04:35 +02:00 committed by GitHub
parent 24cc6c33de
commit 07f4a9040a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 692 additions and 593 deletions

View File

@ -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(()));
}
}

View File

@ -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();