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

Commit fec706d

Browse files
committed
single-pool-cli: add reactivate
1 parent 07dc52e commit fec706d

File tree

3 files changed

+101
-1
lines changed

3 files changed

+101
-1
lines changed

single-pool/cli/src/cli.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ pub enum Command {
7676
/// along with the cluster-configured minimum stake delegation
7777
Initialize(InitializeCli),
7878

79+
/// Permissionlessly re-stake the pool stake account in the case when it has been deactivated.
80+
/// This may happen if the validator is force-deactivated, and then later reactivated using
81+
/// the same address for its vote account.
82+
Reactivate(ReactivateCli),
83+
7984
/// Deposit delegated stake into a pool in exchange for pool tokens, closing out
8085
/// the original stake account. Provide either a stake account address, or a
8186
/// pool or vote account address along with the --default-stake-account flag to
@@ -114,6 +119,22 @@ pub struct InitializeCli {
114119
pub skip_metadata: bool,
115120
}
116121

122+
#[derive(Clone, Debug, Args)]
123+
#[clap(group(pool_source_group()))]
124+
pub struct ReactivateCli {
125+
/// The pool to reactivate
126+
#[clap(short, long = "pool", value_parser = |p: &str| parse_address(p, "pool_address"))]
127+
pub pool_address: Option<Pubkey>,
128+
129+
/// The vote account corresponding to the pool to reactivate
130+
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
131+
pub vote_account_address: Option<Pubkey>,
132+
133+
// backdoor for testing, theres no reason to ever use this
134+
#[clap(long, hide = true)]
135+
pub yolo: bool,
136+
}
137+
117138
#[derive(Clone, Debug, Args)]
118139
#[clap(group(ArgGroup::new("stake-source").required(true).args(&["stake-account-address", "default-stake-account"])))]
119140
#[clap(group(pool_source_group().required(false)))]

single-pool/cli/src/main.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ impl Command {
6161
pub async fn execute(self, config: &Config) -> CommandResult {
6262
match self {
6363
Command::Initialize(command_config) => command_initialize(config, command_config).await,
64+
Command::Reactivate(command_config) => command_reactivate(config, command_config).await,
6465
Command::Deposit(command_config) => command_deposit(config, command_config).await,
6566
Command::Withdraw(command_config) => command_withdraw(config, command_config).await,
6667
Command::CreateTokenMetadata(command_config) => {
@@ -157,6 +158,61 @@ async fn command_initialize(config: &Config, command_config: InitializeCli) -> C
157158
))
158159
}
159160

161+
// reactivate stake account
162+
async fn command_reactivate(config: &Config, command_config: ReactivateCli) -> CommandResult {
163+
let payer = config.fee_payer()?;
164+
let pool_address = pool_address_from_args(
165+
command_config.pool_address,
166+
command_config.vote_account_address,
167+
);
168+
169+
println_display(
170+
config,
171+
format!("Reactivating stake account for pool {}\n", pool_address),
172+
);
173+
174+
let vote_account_address =
175+
if let Some(pool_data) = config.program_client.get_account(pool_address).await? {
176+
try_from_slice_unchecked::<SinglePool>(&pool_data.data)?.vote_account_address
177+
} else {
178+
return Err(format!("Pool {} has not been initialized", pool_address).into());
179+
};
180+
181+
// the only reason this check is skippable is for testing, otherwise theres no reason
182+
if !command_config.yolo {
183+
let current_epoch = config.rpc_client.get_epoch_info().await?.epoch;
184+
let pool_stake_address = find_pool_stake_address(&single_pool::id(), &pool_address);
185+
let pool_stake_deactivated = quarantine::get_stake_info(config, &pool_stake_address)
186+
.await?
187+
.unwrap()
188+
.1
189+
.delegation
190+
.deactivation_epoch
191+
< current_epoch;
192+
193+
if !pool_stake_deactivated {
194+
return Err("Pool stake account is not deactivated".into());
195+
}
196+
}
197+
198+
let instruction =
199+
single_pool::instruction::reactivate_pool(&single_pool::id(), &vote_account_address);
200+
let transaction = Transaction::new_signed_with_payer(
201+
&[instruction],
202+
Some(&payer.pubkey()),
203+
&vec![payer],
204+
config.program_client.get_latest_blockhash().await?,
205+
);
206+
207+
let signature = process_transaction(config, transaction).await?;
208+
209+
Ok(format_output(
210+
config,
211+
"Reactivate".to_string(),
212+
SignatureOutput { signature },
213+
))
214+
}
215+
160216
// deposit stake
161217
async fn command_deposit(config: &Config, command_config: DepositCli) -> CommandResult {
162218
let payer = config.fee_payer()?;
@@ -572,7 +628,7 @@ async fn command_update_metadata(
572628
.into());
573629
}
574630
} else {
575-
// we know the pool exists so the vote accound must exist
631+
// we know the pool exists so the vote account must exist
576632
unreachable!();
577633
}
578634

single-pool/cli/tests/test.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,29 @@ async fn create_and_delegate_stake_account(
248248
stake_account.pubkey()
249249
}
250250

251+
#[tokio::test]
252+
#[serial]
253+
async fn reactivate() {
254+
let env = setup(true).await;
255+
256+
// setting up a test validator for this to succeed is hell, and success is tested in program tests
257+
// so we just make sure the cli can send a well-formed instruction
258+
let output = Command::new(SVSP_CLI)
259+
.args([
260+
"reactivate",
261+
"-C",
262+
&env.config_file_path,
263+
"--vote-account",
264+
&env.vote_account.to_string(),
265+
"--yolo",
266+
])
267+
.output()
268+
.unwrap();
269+
assert!(String::from_utf8(output.stderr)
270+
.unwrap()
271+
.contains("custom program error: 0xc"));
272+
}
273+
251274
#[test_case(true; "default_stake")]
252275
#[test_case(false; "normal_stake")]
253276
#[tokio::test]

0 commit comments

Comments
 (0)