Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 07dc52e

Browse files
committed
single-pool: add reactivate instruction
1 parent 5088633 commit 07dc52e

File tree

4 files changed

+256
-0
lines changed

4 files changed

+256
-0
lines changed

single-pool/program/src/instruction.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ pub enum SinglePoolInstruction {
4343
/// 12. `[]` Stake program
4444
InitializePool,
4545

46+
/// Restake the pool stake account if its validator is force-deactivated and later
47+
/// recreated with the same vote account.
48+
///
49+
/// 0. `[]` Validator vote account
50+
/// 1. `[]` Pool account
51+
/// 2. `[w]` Pool stake account
52+
/// 3. `[]` Pool stake authority
53+
/// 4. `[]` Clock sysvar
54+
/// 5. `[]` Stake history sysvar
55+
/// 6. `[]` Stake config sysvar
56+
/// 7. `[]` Stake program
57+
ReactivatePool,
58+
4659
/// Deposit stake into the pool. The output is a "pool" token representing fractional
4760
/// ownership of the pool stake. Inputs are converted to the current ratio.
4861
///
@@ -177,6 +190,33 @@ pub fn initialize_pool(program_id: &Pubkey, vote_account_address: &Pubkey) -> In
177190
}
178191
}
179192

193+
/// Creates a `ReactivatePool` instruction.
194+
pub fn reactivate_pool(program_id: &Pubkey, vote_account_address: &Pubkey) -> Instruction {
195+
let pool_address = find_pool_address(program_id, vote_account_address);
196+
197+
let data = SinglePoolInstruction::ReactivatePool.try_to_vec().unwrap();
198+
let accounts = vec![
199+
AccountMeta::new_readonly(*vote_account_address, false),
200+
AccountMeta::new_readonly(pool_address, false),
201+
AccountMeta::new(find_pool_stake_address(program_id, &pool_address), false),
202+
AccountMeta::new_readonly(
203+
find_pool_stake_authority_address(program_id, &pool_address),
204+
false,
205+
),
206+
AccountMeta::new_readonly(sysvar::clock::id(), false),
207+
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
208+
#[allow(deprecated)]
209+
AccountMeta::new_readonly(stake::config::id(), false),
210+
AccountMeta::new_readonly(stake::program::id(), false),
211+
];
212+
213+
Instruction {
214+
program_id: *program_id,
215+
accounts,
216+
data,
217+
}
218+
}
219+
180220
/// Creates all necessary instructions to deposit stake.
181221
pub fn deposit(
182222
program_id: &Pubkey,

single-pool/program/src/processor.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,64 @@ impl Processor {
707707
Ok(())
708708
}
709709

710+
fn process_reactivate_pool(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
711+
let account_info_iter = &mut accounts.iter();
712+
let vote_account_info = next_account_info(account_info_iter)?;
713+
let pool_info = next_account_info(account_info_iter)?;
714+
let pool_stake_info = next_account_info(account_info_iter)?;
715+
let pool_stake_authority_info = next_account_info(account_info_iter)?;
716+
let clock_info = next_account_info(account_info_iter)?;
717+
let clock = &Clock::from_account_info(clock_info)?;
718+
let stake_history_info = next_account_info(account_info_iter)?;
719+
let stake_config_info = next_account_info(account_info_iter)?;
720+
let stake_program_info = next_account_info(account_info_iter)?;
721+
722+
check_vote_account(vote_account_info)?;
723+
check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
724+
725+
SinglePool::from_account_info(pool_info, program_id)?;
726+
727+
check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
728+
let stake_authority_bump_seed = check_pool_stake_authority_address(
729+
program_id,
730+
pool_info.key,
731+
pool_stake_authority_info.key,
732+
)?;
733+
check_stake_program(stake_program_info.key)?;
734+
735+
let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
736+
if pool_stake_state.delegation.deactivation_epoch >= clock.epoch {
737+
return Err(SinglePoolError::WrongStakeState.into());
738+
}
739+
740+
let stake_authority_seeds = &[
741+
POOL_STAKE_AUTHORITY_PREFIX,
742+
pool_info.key.as_ref(),
743+
&[stake_authority_bump_seed],
744+
];
745+
let stake_authority_signers = &[&stake_authority_seeds[..]];
746+
747+
// delegate stake so it activates
748+
invoke_signed(
749+
&stake::instruction::delegate_stake(
750+
pool_stake_info.key,
751+
pool_stake_authority_info.key,
752+
vote_account_info.key,
753+
),
754+
&[
755+
pool_stake_info.clone(),
756+
vote_account_info.clone(),
757+
clock_info.clone(),
758+
stake_history_info.clone(),
759+
stake_config_info.clone(),
760+
pool_stake_authority_info.clone(),
761+
],
762+
stake_authority_signers,
763+
)?;
764+
765+
Ok(())
766+
}
767+
710768
fn process_deposit_stake(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
711769
let account_info_iter = &mut accounts.iter();
712770
let pool_info = next_account_info(account_info_iter)?;
@@ -1120,6 +1178,10 @@ impl Processor {
11201178
msg!("Instruction: InitializePool");
11211179
Self::process_initialize_pool(program_id, accounts)
11221180
}
1181+
SinglePoolInstruction::ReactivatePool => {
1182+
msg!("Instruction: ReactivatePool");
1183+
Self::process_reactivate_pool(program_id, accounts)
1184+
}
11231185
SinglePoolInstruction::DepositStake => {
11241186
msg!("Instruction: DepositStake");
11251187
Self::process_deposit_stake(program_id, accounts)

single-pool/program/tests/accounts.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ async fn fail_account_checks(test_mode: TestMode) {
196196

197197
// make an individual instruction for all program instructions
198198
// the match is just so this will error if new instructions are added
199+
// if you are reading this because of that error, add the case to the `consistent_account_order` test!!!
199200
fn make_basic_instruction(
200201
accounts: &SinglePoolAccounts,
201202
instruction_type: SinglePoolInstruction,
@@ -204,6 +205,9 @@ fn make_basic_instruction(
204205
SinglePoolInstruction::InitializePool => {
205206
instruction::initialize_pool(&id(), &accounts.vote_account.pubkey())
206207
}
208+
SinglePoolInstruction::ReactivatePool => {
209+
instruction::reactivate_pool(&id(), &accounts.vote_account.pubkey())
210+
}
207211
SinglePoolInstruction::DepositStake => instruction::deposit_stake(
208212
&id(),
209213
&accounts.pool,
@@ -258,6 +262,7 @@ fn consistent_account_order() {
258262

259263
let instructions = vec![
260264
make_basic_instruction(&accounts, SinglePoolInstruction::InitializePool),
265+
make_basic_instruction(&accounts, SinglePoolInstruction::ReactivatePool),
261266
make_basic_instruction(&accounts, SinglePoolInstruction::DepositStake),
262267
make_basic_instruction(
263268
&accounts,
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#![allow(clippy::integer_arithmetic)]
2+
#![cfg(feature = "test-sbf")]
3+
4+
mod helpers;
5+
6+
use {
7+
bincode::serialize,
8+
helpers::*,
9+
solana_program_test::*,
10+
solana_sdk::{
11+
account::AccountSharedData,
12+
signature::Signer,
13+
stake::state::{Delegation, Stake, StakeState},
14+
transaction::Transaction,
15+
},
16+
spl_single_validator_pool::{error::SinglePoolError, id, instruction},
17+
test_case::test_case,
18+
};
19+
20+
#[tokio::test]
21+
async fn success() {
22+
let mut context = program_test().start_with_context().await;
23+
let accounts = SinglePoolAccounts::default();
24+
accounts
25+
.initialize_for_deposit(&mut context, TEST_STAKE_AMOUNT, None)
26+
.await;
27+
advance_epoch(&mut context).await;
28+
29+
// deactivate the pool stake account
30+
let (meta, stake, _) =
31+
get_stake_account(&mut context.banks_client, &accounts.stake_account).await;
32+
let delegation = Delegation {
33+
activation_epoch: 0,
34+
deactivation_epoch: 0,
35+
..stake.unwrap().delegation
36+
};
37+
let account_data = serialize(&StakeState::Stake(
38+
meta,
39+
Stake {
40+
delegation,
41+
..stake.unwrap()
42+
},
43+
))
44+
.unwrap();
45+
46+
let mut stake_account = get_account(&mut context.banks_client, &accounts.stake_account).await;
47+
stake_account.data = account_data;
48+
context.set_account(
49+
&accounts.stake_account,
50+
&AccountSharedData::from(stake_account),
51+
);
52+
53+
// make sure deposit fails
54+
let instructions = instruction::deposit(
55+
&id(),
56+
&accounts.pool,
57+
&accounts.alice_stake.pubkey(),
58+
&accounts.alice_token,
59+
&accounts.alice.pubkey(),
60+
&accounts.alice.pubkey(),
61+
);
62+
let transaction = Transaction::new_signed_with_payer(
63+
&instructions,
64+
Some(&context.payer.pubkey()),
65+
&[&context.payer, &accounts.alice],
66+
context.last_blockhash,
67+
);
68+
69+
let e = context
70+
.banks_client
71+
.process_transaction(transaction)
72+
.await
73+
.unwrap_err();
74+
check_error(e, SinglePoolError::WrongStakeState);
75+
76+
// reactivate
77+
let instruction = instruction::reactivate_pool(&id(), &accounts.vote_account.pubkey());
78+
let transaction = Transaction::new_signed_with_payer(
79+
&[instruction],
80+
Some(&context.payer.pubkey()),
81+
&[&context.payer],
82+
context.last_blockhash,
83+
);
84+
85+
context
86+
.banks_client
87+
.process_transaction(transaction)
88+
.await
89+
.unwrap();
90+
91+
advance_epoch(&mut context).await;
92+
93+
// deposit works again
94+
let instructions = instruction::deposit(
95+
&id(),
96+
&accounts.pool,
97+
&accounts.alice_stake.pubkey(),
98+
&accounts.alice_token,
99+
&accounts.alice.pubkey(),
100+
&accounts.alice.pubkey(),
101+
);
102+
let transaction = Transaction::new_signed_with_payer(
103+
&instructions,
104+
Some(&context.payer.pubkey()),
105+
&[&context.payer, &accounts.alice],
106+
context.last_blockhash,
107+
);
108+
109+
context
110+
.banks_client
111+
.process_transaction(transaction)
112+
.await
113+
.unwrap();
114+
115+
assert!(context
116+
.banks_client
117+
.get_account(accounts.alice_stake.pubkey())
118+
.await
119+
.expect("get_account")
120+
.is_none());
121+
}
122+
123+
#[test_case(true; "activated")]
124+
#[test_case(false; "activating")]
125+
#[tokio::test]
126+
async fn fail_not_deactivated(activate: bool) {
127+
let mut context = program_test().start_with_context().await;
128+
let accounts = SinglePoolAccounts::default();
129+
accounts.initialize(&mut context).await;
130+
131+
if activate {
132+
advance_epoch(&mut context).await;
133+
}
134+
135+
let instruction = instruction::reactivate_pool(&id(), &accounts.vote_account.pubkey());
136+
let transaction = Transaction::new_signed_with_payer(
137+
&[instruction],
138+
Some(&context.payer.pubkey()),
139+
&[&context.payer],
140+
context.last_blockhash,
141+
);
142+
143+
let e = context
144+
.banks_client
145+
.process_transaction(transaction)
146+
.await
147+
.unwrap_err();
148+
check_error(e, SinglePoolError::WrongStakeState);
149+
}

0 commit comments

Comments
 (0)