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},
|
instruction::{self, LockupArgs},
|
||||||
state::{Authorized, Lockup, StakeAuthorize},
|
state::{Authorized, Lockup, StakeAuthorize},
|
||||||
},
|
},
|
||||||
system_program,
|
stake_history::{StakeHistory, StakeHistoryEntry},
|
||||||
sysvar::{self, stake_history::StakeHistory},
|
system_program, sysvar,
|
||||||
},
|
},
|
||||||
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
|
solana_vote_program::vote_state::{self, VoteState, VoteStateVersions},
|
||||||
std::{collections::HashSet, str::FromStr},
|
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,
|
sysvar::SysvarId,
|
||||||
transaction_context::TransactionContext,
|
transaction_context::TransactionContext,
|
||||||
},
|
},
|
||||||
std::iter::FromIterator,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[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]
|
#[test]
|
||||||
fn test_lockup_is_expired() {
|
fn test_lockup_is_expired() {
|
||||||
let custodian = solana_sdk::pubkey::new_rand();
|
let custodian = solana_sdk::pubkey::new_rand();
|
||||||
|
|
Loading…
Reference in New Issue