diff --git a/stake-pool/cli/src/main.rs b/stake-pool/cli/src/main.rs index 5de6efc6..3532cb88 100644 --- a/stake-pool/cli/src/main.rs +++ b/stake-pool/cli/src/main.rs @@ -14,7 +14,7 @@ use { input_validators::{is_amount, is_keypair, is_parsable, is_pubkey, is_url}, keypair::signer_from_path, }, - solana_client::{rpc_client::RpcClient, rpc_response::StakeActivationState}, + solana_client::rpc_client::RpcClient, solana_program::{ borsh::get_packed_len, instruction::Instruction, program_pack::Pack, pubkey::Pubkey, }, @@ -28,9 +28,9 @@ use { spl_stake_pool::{ self, borsh::get_instance_packed_len, - find_deposit_authority_program_address, find_stake_program_address, - find_transient_stake_program_address, find_withdraw_authority_program_address, - stake_program::{self, StakeAuthorize, StakeState}, + find_stake_program_address, find_transient_stake_program_address, + find_withdraw_authority_program_address, + stake_program::{self, StakeState}, state::{Fee, StakePool, ValidatorList}, MAX_VALIDATORS_TO_UPDATE, }, @@ -42,6 +42,7 @@ struct Config { verbose: bool, manager: Box, staker: Box, + depositor: Option>, token_owner: Box, fee_payer: Box, dry_run: bool, @@ -94,7 +95,12 @@ fn send_transaction( Ok(()) } -fn command_create_pool(config: &Config, fee: Fee, max_validators: u32) -> CommandResult { +fn command_create_pool( + config: &Config, + deposit_authority: Option, + fee: Fee, + max_validators: u32, +) -> CommandResult { let reserve_stake = Keypair::new(); println!("Creating reserve stake {}", reserve_stake.pubkey()); @@ -224,6 +230,7 @@ fn command_create_pool(config: &Config, fee: Fee, max_validators: u32) -> Comman &mint_account.pubkey(), &pool_fee_account.pubkey(), &spl_token::id(), + deposit_authority, fee, max_validators, )?, @@ -286,7 +293,17 @@ fn command_vsa_create( } fn command_vsa_add(config: &Config, stake_pool_address: &Pubkey, stake: &Pubkey) -> CommandResult { - if config.rpc_client.get_stake_activation(*stake, None)?.state != StakeActivationState::Active { + let stake_state = get_stake_state(&config.rpc_client, &stake)?; + if let stake_program::StakeState::Stake(meta, _stake) = stake_state { + if meta.authorized.withdrawer != config.staker.pubkey() { + let error = format!( + "Stake account withdraw authority must be the staker {}, actual {}", + config.staker.pubkey(), + meta.authorized.withdrawer + ); + return Err(error.into()); + } + } else { return Err("Stake account is not active.".into()); } @@ -299,34 +316,16 @@ fn command_vsa_add(config: &Config, stake_pool_address: &Pubkey, stake: &Pubkey) let mut instructions: Vec = vec![]; let mut signers = vec![config.fee_payer.as_ref(), config.staker.as_ref()]; - // Calculate Deposit and Withdraw stake pool authorities - let pool_deposit_authority = - find_deposit_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; - + // Calculate Withdraw stake pool authorities let pool_withdraw_authority = find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; instructions.extend(vec![ - // Set Withdrawer on stake account to Deposit authority of the stake pool - stake_program::authorize( - &stake, - &config.staker.pubkey(), - &pool_deposit_authority, - StakeAuthorize::Withdrawer, - ), - // Set Staker on stake account to Deposit authority of the stake pool - stake_program::authorize( - &stake, - &config.staker.pubkey(), - &pool_deposit_authority, - StakeAuthorize::Staker, - ), // Add validator stake account to the pool spl_stake_pool::instruction::add_validator_to_pool( &spl_stake_pool::id(), &stake_pool_address, &config.staker.pubkey(), - &pool_deposit_authority, &pool_withdraw_authority, &stake_pool.validator_list, &stake, @@ -588,42 +587,49 @@ fn command_deposit( }, )?; - // Calculate Deposit and Withdraw stake pool authorities - let pool_deposit_authority = - find_deposit_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; - let pool_withdraw_authority = find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; - instructions.extend(vec![ - // Set Withdrawer on stake account to Deposit authority of the stake pool - stake_program::authorize( - &stake, - &config.staker.pubkey(), - &pool_deposit_authority, - StakeAuthorize::Withdrawer, - ), - // Set Staker on stake account to Deposit authority of the stake pool - stake_program::authorize( - &stake, - &config.staker.pubkey(), - &pool_deposit_authority, - StakeAuthorize::Staker, - ), - // Add stake account to the pool - spl_stake_pool::instruction::deposit( + let mut deposit_instructions = if let Some(deposit_authority) = config.depositor.as_ref() { + signers.push(deposit_authority.as_ref()); + if deposit_authority.pubkey() != stake_pool.deposit_authority { + let error = format!( + "Invalid deposit authority specified, expected {}, received {}", + stake_pool.deposit_authority, + deposit_authority.pubkey() + ); + return Err(error.into()); + } + + spl_stake_pool::instruction::deposit_with_authority( &spl_stake_pool::id(), &stake_pool_address, &stake_pool.validator_list, - &pool_deposit_authority, + &deposit_authority.pubkey(), &pool_withdraw_authority, &stake, + &config.staker.pubkey(), &validator_stake_account, &token_receiver, &stake_pool.pool_mint, &spl_token::id(), - )?, - ]); + ) + } else { + spl_stake_pool::instruction::deposit( + &spl_stake_pool::id(), + &stake_pool_address, + &stake_pool.validator_list, + &pool_withdraw_authority, + &stake, + &config.staker.pubkey(), + &validator_stake_account, + &token_receiver, + &stake_pool.pool_mint, + &spl_token::id(), + ) + }; + + instructions.append(&mut deposit_instructions); let mut transaction = Transaction::new_with_payer(&instructions, Some(&config.fee_payer.pubkey())); @@ -1130,6 +1136,17 @@ fn main() { Defaults to the client keypair.", ), ) + .arg( + Arg::with_name("depositor") + .long("depositor") + .value_name("KEYPAIR") + .validator(is_keypair) + .takes_value(true) + .help( + "Specify the stake pool depositor. \ + This may be a keypair file, the ASK keyword.", + ), + ) .arg( Arg::with_name("token_owner") .long("token-owner") @@ -1186,6 +1203,15 @@ fn main() { .required(true) .help("Max number of validators included in the stake pool"), ) + .arg( + Arg::with_name("deposit_authority") + .long("deposit-authority") + .short("a") + .validator(is_pubkey) + .value_name("DEPOSIT_AUTHORITY_ADDRESS") + .takes_value(true) + .help("Deposit authority required to sign all deposits into the stake pool"), + ) ) .subcommand(SubCommand::with_name("create-validator-stake") .about("Create a new stake account to use with the pool. Must be signed by the pool staker.") @@ -1515,6 +1541,22 @@ fn main() { eprintln!("error: {}", e); exit(1); }); + let depositor = if matches.is_present("depositor") { + Some( + signer_from_path( + &matches, + &cli_config.keypair_path, + "depositor", + &mut wallet_manager, + ) + .unwrap_or_else(|e| { + eprintln!("error: {}", e); + exit(1); + }), + ) + } else { + None + }; let manager = signer_from_path( &matches, &cli_config.keypair_path, @@ -1554,6 +1596,7 @@ fn main() { verbose, manager, staker, + depositor, token_owner, fee_payer, dry_run, @@ -1563,11 +1606,13 @@ fn main() { let _ = match matches.subcommand() { ("create-pool", Some(arg_matches)) => { + let deposit_authority = pubkey_of(arg_matches, "deposit_authority"); let numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64); let denominator = value_t_or_exit!(arg_matches, "fee_denominator", u64); let max_validators = value_t_or_exit!(arg_matches, "max_validators", u32); command_create_pool( &config, + deposit_authority, Fee { denominator, numerator, diff --git a/stake-pool/program/src/instruction.rs b/stake-pool/program/src/instruction.rs index b8f3f795..ebf2bf52 100644 --- a/stake-pool/program/src/instruction.rs +++ b/stake-pool/program/src/instruction.rs @@ -4,7 +4,8 @@ use { crate::{ - find_stake_program_address, find_transient_stake_program_address, stake_program, state::Fee, + find_deposit_authority_program_address, find_stake_program_address, + find_transient_stake_program_address, stake_program, state::Fee, }, borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, solana_program::{ @@ -32,6 +33,9 @@ pub enum StakePoolInstruction { /// 7. `[]` Clock sysvar /// 8. `[]` Rent sysvar /// 9. `[]` Token program id + /// 10. `[]` (Optional) Deposit authority that must sign all deposits. + /// Defaults to the program address generated using + /// `find_deposit_authority_program_address`, making deposits permissionless. Initialize { /// Fee assessed as percentage of perceived rewards #[allow(dead_code)] // but it's not @@ -70,13 +74,13 @@ pub enum StakePoolInstruction { /// /// 0. `[w]` Stake pool /// 1. `[s]` Staker - /// 2. `[]` Stake pool deposit authority - /// 3. `[]` Stake pool withdraw authority - /// 4. `[w]` Validator stake list storage account - /// 5. `[w]` Stake account to add to the pool, its withdraw authority should be set to stake pool deposit - /// 6. `[]` Clock sysvar - /// 7. '[]' Sysvar stake history account - /// 8. `[]` Stake program + /// 2. `[]` Stake pool withdraw authority + /// 3. `[w]` Validator stake list storage account + /// 4. `[w]` Stake account to add to the pool, its withdraw authority must + /// be set to the staker + /// 5. `[]` Clock sysvar + /// 6. '[]' Sysvar stake history account + /// 7. `[]` Stake program AddValidatorToPool, /// (Staker only) Removes validator from the pool @@ -195,7 +199,7 @@ pub enum StakePoolInstruction { /// 1. `[w]` Validator stake list storage account /// 2. `[]` Stake pool deposit authority /// 3. `[]` Stake pool withdraw authority - /// 4. `[w]` Stake account to join the pool (withdraw should be set to stake pool deposit) + /// 4. `[w]` Stake account to join the pool (withdraw authority for the stake account should be first set to the stake pool deposit authority) /// 5. `[w]` Validator stake account for the stake account to be merged with /// 6. `[w]` User account to receive pool tokens /// 8. `[w]` Pool token mint account @@ -267,6 +271,7 @@ pub fn initialize( pool_mint: &Pubkey, manager_pool_account: &Pubkey, token_program_id: &Pubkey, + deposit_authority: Option, fee: Fee, max_validators: u32, ) -> Result { @@ -275,7 +280,7 @@ pub fn initialize( max_validators, }; let data = init_data.try_to_vec()?; - let accounts = vec![ + let mut accounts = vec![ AccountMeta::new(*stake_pool, true), AccountMeta::new_readonly(*manager, true), AccountMeta::new_readonly(*staker, false), @@ -287,6 +292,9 @@ pub fn initialize( AccountMeta::new_readonly(sysvar::rent::id(), false), AccountMeta::new_readonly(*token_program_id, false), ]; + if let Some(deposit_authority) = deposit_authority { + accounts.push(AccountMeta::new_readonly(deposit_authority, true)); + } Ok(Instruction { program_id: *program_id, accounts, @@ -328,7 +336,6 @@ pub fn add_validator_to_pool( program_id: &Pubkey, stake_pool: &Pubkey, staker: &Pubkey, - stake_pool_deposit: &Pubkey, stake_pool_withdraw: &Pubkey, validator_list: &Pubkey, stake_account: &Pubkey, @@ -336,7 +343,6 @@ pub fn add_validator_to_pool( let accounts = vec![ AccountMeta::new(*stake_pool, false), AccountMeta::new_readonly(*staker, true), - AccountMeta::new_readonly(*stake_pool_deposit, false), AccountMeta::new_readonly(*stake_pool_withdraw, false), AccountMeta::new(*validator_list, false), AccountMeta::new(*stake_account, false), @@ -529,25 +535,28 @@ pub fn update_stake_pool_balance( } } -/// Creates a 'Deposit' instruction. +/// Creates instructions required to deposit into a stake pool, given a stake +/// account owned by the user. pub fn deposit( program_id: &Pubkey, stake_pool: &Pubkey, validator_list_storage: &Pubkey, - stake_pool_deposit: &Pubkey, - stake_pool_withdraw: &Pubkey, - stake_to_join: &Pubkey, + stake_pool_withdraw_authority: &Pubkey, + deposit_stake_address: &Pubkey, + deposit_stake_withdraw_authority: &Pubkey, validator_stake_accont: &Pubkey, pool_tokens_to: &Pubkey, pool_mint: &Pubkey, token_program_id: &Pubkey, -) -> Result { +) -> Vec { + let stake_pool_deposit_authority = + find_deposit_authority_program_address(program_id, stake_pool).0; let accounts = vec![ AccountMeta::new(*stake_pool, false), AccountMeta::new(*validator_list_storage, false), - AccountMeta::new_readonly(*stake_pool_deposit, false), - AccountMeta::new_readonly(*stake_pool_withdraw, false), - AccountMeta::new(*stake_to_join, false), + AccountMeta::new_readonly(stake_pool_deposit_authority, false), + AccountMeta::new_readonly(*stake_pool_withdraw_authority, false), + AccountMeta::new(*deposit_stake_address, false), AccountMeta::new(*validator_stake_accont, false), AccountMeta::new(*pool_tokens_to, false), AccountMeta::new(*pool_mint, false), @@ -556,11 +565,76 @@ pub fn deposit( AccountMeta::new_readonly(*token_program_id, false), AccountMeta::new_readonly(stake_program::id(), false), ]; - Ok(Instruction { - program_id: *program_id, - accounts, - data: StakePoolInstruction::Deposit.try_to_vec()?, - }) + vec![ + stake_program::authorize( + deposit_stake_address, + deposit_stake_withdraw_authority, + &stake_pool_deposit_authority, + stake_program::StakeAuthorize::Staker, + ), + stake_program::authorize( + deposit_stake_address, + deposit_stake_withdraw_authority, + &stake_pool_deposit_authority, + stake_program::StakeAuthorize::Withdrawer, + ), + Instruction { + program_id: *program_id, + accounts, + data: StakePoolInstruction::Deposit.try_to_vec().unwrap(), + }, + ] +} + +/// Creates instructions required to deposit into a stake pool, given a stake +/// account owned by the user. The difference with `deposit()` is that a deposit +/// authority must sign this instruction, which is required for private pools. +pub fn deposit_with_authority( + program_id: &Pubkey, + stake_pool: &Pubkey, + validator_list_storage: &Pubkey, + stake_pool_deposit_authority: &Pubkey, + stake_pool_withdraw_authority: &Pubkey, + deposit_stake_address: &Pubkey, + deposit_stake_withdraw_authority: &Pubkey, + validator_stake_accont: &Pubkey, + pool_tokens_to: &Pubkey, + pool_mint: &Pubkey, + token_program_id: &Pubkey, +) -> Vec { + let accounts = vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new(*validator_list_storage, false), + AccountMeta::new_readonly(*stake_pool_deposit_authority, true), + AccountMeta::new_readonly(*stake_pool_withdraw_authority, false), + AccountMeta::new(*deposit_stake_address, false), + AccountMeta::new(*validator_stake_accont, false), + AccountMeta::new(*pool_tokens_to, false), + AccountMeta::new(*pool_mint, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(sysvar::stake_history::id(), false), + AccountMeta::new_readonly(*token_program_id, false), + AccountMeta::new_readonly(stake_program::id(), false), + ]; + vec![ + stake_program::authorize( + deposit_stake_address, + deposit_stake_withdraw_authority, + stake_pool_deposit_authority, + stake_program::StakeAuthorize::Staker, + ), + stake_program::authorize( + deposit_stake_address, + deposit_stake_withdraw_authority, + stake_pool_deposit_authority, + stake_program::StakeAuthorize::Withdrawer, + ), + Instruction { + program_id: *program_id, + accounts, + data: StakePoolInstruction::Deposit.try_to_vec().unwrap(), + }, + ] } /// Creates a 'withdraw' instruction. diff --git a/stake-pool/program/src/processor.rs b/stake-pool/program/src/processor.rs index 36bf2e1f..53515cfd 100644 --- a/stake-pool/program/src/processor.rs +++ b/stake-pool/program/src/processor.rs @@ -4,6 +4,7 @@ use { crate::{ borsh::try_from_slice_unchecked, error::StakePoolError, + find_deposit_authority_program_address, instruction::StakePoolInstruction, minimum_reserve_lamports, minimum_stake_lamports, stake_program, state::{AccountType, Fee, StakePool, ValidatorList, ValidatorStakeInfo}, @@ -203,10 +204,14 @@ impl Processor { let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]]; let signers = &[&authority_signature_seeds[..]]; - let ix = + let split_instruction = stake_program::split_only(stake_account.key, authority.key, amount, split_stake.key); - invoke_signed(&ix, &[stake_account, split_stake, authority], signers) + invoke_signed( + &split_instruction, + &[stake_account, split_stake, authority], + signers, + ) } /// Issue a stake_merge instruction. @@ -226,10 +231,11 @@ impl Processor { let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]]; let signers = &[&authority_signature_seeds[..]]; - let ix = stake_program::merge(destination_account.key, source_account.key, authority.key); + let merge_instruction = + stake_program::merge(destination_account.key, source_account.key, authority.key); invoke_signed( - &ix, + &merge_instruction, &[ destination_account, source_account, @@ -242,16 +248,53 @@ impl Processor { ) } - /// Issue a stake_set_manager instruction. - #[allow(clippy::too_many_arguments)] + /// Issue stake_program::authorize instructions to update both authorities fn stake_authorize<'a>( + stake_account: AccountInfo<'a>, + stake_authority: AccountInfo<'a>, + new_stake_authority: &Pubkey, + clock: AccountInfo<'a>, + stake_program_info: AccountInfo<'a>, + ) -> Result<(), ProgramError> { + let authorize_instruction = stake_program::authorize( + stake_account.key, + stake_authority.key, + new_stake_authority, + stake_program::StakeAuthorize::Staker, + ); + + invoke( + &authorize_instruction, + &[ + stake_account.clone(), + clock.clone(), + stake_authority.clone(), + stake_program_info.clone(), + ], + )?; + + let authorize_instruction = stake_program::authorize( + stake_account.key, + stake_authority.key, + new_stake_authority, + stake_program::StakeAuthorize::Withdrawer, + ); + + invoke( + &authorize_instruction, + &[stake_account, clock, stake_authority, stake_program_info], + ) + } + + /// Issue stake_program::authorize instructions to update both authorities + #[allow(clippy::too_many_arguments)] + fn stake_authorize_signed<'a>( stake_pool: &Pubkey, stake_account: AccountInfo<'a>, - authority: AccountInfo<'a>, + stake_authority: AccountInfo<'a>, authority_type: &[u8], bump_seed: u8, - new_staker: &Pubkey, - staker_auth: stake_program::StakeAuthorize, + new_stake_authority: &Pubkey, clock: AccountInfo<'a>, stake_program_info: AccountInfo<'a>, ) -> Result<(), ProgramError> { @@ -259,12 +302,33 @@ impl Processor { let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]]; let signers = &[&authority_signature_seeds[..]]; - let ix = - stake_program::authorize(stake_account.key, authority.key, new_staker, staker_auth); + let authorize_instruction = stake_program::authorize( + stake_account.key, + stake_authority.key, + new_stake_authority, + stake_program::StakeAuthorize::Staker, + ); invoke_signed( - &ix, - &[stake_account, clock, authority, stake_program_info], + &authorize_instruction, + &[ + stake_account.clone(), + clock.clone(), + stake_authority.clone(), + stake_program_info.clone(), + ], + signers, + )?; + + let authorize_instruction = stake_program::authorize( + stake_account.key, + stake_authority.key, + new_stake_authority, + stake_program::StakeAuthorize::Withdrawer, + ); + invoke_signed( + &authorize_instruction, + &[stake_account, clock, stake_authority, stake_program_info], signers, ) } @@ -414,8 +478,10 @@ impl Processor { return Err(StakePoolError::WrongAccountMint.into()); } - let (_, deposit_bump_seed) = - crate::find_deposit_authority_program_address(program_id, stake_pool_info.key); + let deposit_authority = match next_account_info(account_info_iter) { + Ok(deposit_authority_info) => *deposit_authority_info.key, + Err(_) => find_deposit_authority_program_address(program_id, stake_pool_info.key).0, + }; let (withdraw_authority_key, withdraw_bump_seed) = crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key); @@ -475,7 +541,7 @@ impl Processor { stake_pool.manager = *manager_info.key; stake_pool.staker = *staker_info.key; stake_pool.reserve_stake = *reserve_stake_info.key; - stake_pool.deposit_bump_seed = deposit_bump_seed; + stake_pool.deposit_authority = deposit_authority; stake_pool.withdraw_bump_seed = withdraw_bump_seed; stake_pool.validator_list = *validator_list_info.key; stake_pool.pool_mint = *pool_mint_info.key; @@ -594,8 +660,7 @@ impl Processor { let account_info_iter = &mut accounts.iter(); let stake_pool_info = next_account_info(account_info_iter)?; let staker_info = next_account_info(account_info_iter)?; - let deposit_info = next_account_info(account_info_iter)?; - let withdraw_info = next_account_info(account_info_iter)?; + let withdraw_authority_info = next_account_info(account_info_iter)?; let validator_list_info = next_account_info(account_info_iter)?; let stake_account_info = next_account_info(account_info_iter)?; let clock_info = next_account_info(account_info_iter)?; @@ -614,8 +679,11 @@ impl Processor { return Err(StakePoolError::InvalidState.into()); } - stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?; - stake_pool.check_authority_deposit(deposit_info.key, program_id, stake_pool_info.key)?; + stake_pool.check_authority_withdraw( + withdraw_authority_info.key, + program_id, + stake_pool_info.key, + )?; stake_pool.check_staker(staker_info)?; @@ -645,6 +713,11 @@ impl Processor { &vote_account_address, )?; + if meta.lockup != stake_program::Lockup::default() { + msg!("Validator stake account has a lockup"); + return Err(StakePoolError::WrongStakeState.into()); + } + if validator_list.contains(&vote_account_address) { return Err(StakePoolError::ValidatorAlreadyAdded.into()); } @@ -665,22 +738,13 @@ impl Processor { //Self::check_stake_activation(stake_account_info, clock, stake_history)?; // Update Withdrawer and Staker authority to the program withdraw authority - for authority in &[ - stake_program::StakeAuthorize::Withdrawer, - stake_program::StakeAuthorize::Staker, - ] { - Self::stake_authorize( - stake_pool_info.key, - stake_account_info.clone(), - deposit_info.clone(), - AUTHORITY_DEPOSIT, - stake_pool.deposit_bump_seed, - withdraw_info.key, - *authority, - clock_info.clone(), - stake_program_info.clone(), - )?; - } + Self::stake_authorize( + stake_account_info.clone(), + staker_info.clone(), + withdraw_authority_info.key, + clock_info.clone(), + stake_program_info.clone(), + )?; validator_list.validators.push(ValidatorStakeInfo { vote_account_address, @@ -700,7 +764,7 @@ impl Processor { let account_info_iter = &mut accounts.iter(); let stake_pool_info = next_account_info(account_info_iter)?; let staker_info = next_account_info(account_info_iter)?; - let withdraw_info = next_account_info(account_info_iter)?; + let withdraw_authority_info = next_account_info(account_info_iter)?; let new_stake_authority_info = next_account_info(account_info_iter)?; let validator_list_info = next_account_info(account_info_iter)?; let stake_account_info = next_account_info(account_info_iter)?; @@ -717,7 +781,11 @@ impl Processor { return Err(StakePoolError::InvalidState.into()); } - stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?; + stake_pool.check_authority_withdraw( + withdraw_authority_info.key, + program_id, + stake_pool_info.key, + )?; stake_pool.check_staker(staker_info)?; if stake_pool.last_update_epoch < clock.epoch { @@ -772,22 +840,16 @@ impl Processor { return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into()); } - for authority in &[ - stake_program::StakeAuthorize::Withdrawer, - stake_program::StakeAuthorize::Staker, - ] { - Self::stake_authorize( - stake_pool_info.key, - stake_account_info.clone(), - withdraw_info.clone(), - AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, - new_stake_authority_info.key, - *authority, - clock_info.clone(), - stake_program_info.clone(), - )?; - } + Self::stake_authorize_signed( + stake_pool_info.key, + stake_account_info.clone(), + withdraw_authority_info.clone(), + AUTHORITY_WITHDRAW, + stake_pool.withdraw_bump_seed, + new_stake_authority_info.key, + clock_info.clone(), + stake_program_info.clone(), + )?; validator_list .validators @@ -1382,8 +1444,8 @@ impl Processor { let account_info_iter = &mut accounts.iter(); let stake_pool_info = next_account_info(account_info_iter)?; let validator_list_info = next_account_info(account_info_iter)?; - let deposit_info = next_account_info(account_info_iter)?; - let withdraw_info = next_account_info(account_info_iter)?; + let deposit_authority_info = next_account_info(account_info_iter)?; + let withdraw_authority_info = next_account_info(account_info_iter)?; let stake_info = next_account_info(account_info_iter)?; let validator_stake_account_info = next_account_info(account_info_iter)?; let dest_user_info = next_account_info(account_info_iter)?; @@ -1406,8 +1468,12 @@ impl Processor { //Self::check_stake_activation(stake_info, clock, stake_history)?; - stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?; - stake_pool.check_authority_deposit(deposit_info.key, program_id, stake_pool_info.key)?; + stake_pool.check_authority_withdraw( + withdraw_authority_info.key, + program_id, + stake_pool_info.key, + )?; + stake_pool.check_deposit_authority(deposit_authority_info.key)?; stake_pool.check_mint(pool_mint_info)?; if stake_pool.token_program_id != *token_program_info.key { @@ -1451,34 +1517,33 @@ impl Processor { validator_stake_account_info.lamports() ); - Self::stake_authorize( - stake_pool_info.key, - stake_info.clone(), - deposit_info.clone(), - AUTHORITY_DEPOSIT, - stake_pool.deposit_bump_seed, - withdraw_info.key, - stake_program::StakeAuthorize::Withdrawer, - clock_info.clone(), - stake_program_info.clone(), - )?; - - Self::stake_authorize( - stake_pool_info.key, - stake_info.clone(), - deposit_info.clone(), - AUTHORITY_DEPOSIT, - stake_pool.deposit_bump_seed, - withdraw_info.key, - stake_program::StakeAuthorize::Staker, - clock_info.clone(), - stake_program_info.clone(), - )?; + let (deposit_authority_program_address, deposit_bump_seed) = + find_deposit_authority_program_address(program_id, stake_pool_info.key); + if *deposit_authority_info.key == deposit_authority_program_address { + Self::stake_authorize_signed( + stake_pool_info.key, + stake_info.clone(), + deposit_authority_info.clone(), + AUTHORITY_DEPOSIT, + deposit_bump_seed, + withdraw_authority_info.key, + clock_info.clone(), + stake_program_info.clone(), + )?; + } else { + Self::stake_authorize( + stake_info.clone(), + deposit_authority_info.clone(), + withdraw_authority_info.key, + clock_info.clone(), + stake_program_info.clone(), + )?; + } Self::stake_merge( stake_pool_info.key, stake_info.clone(), - withdraw_info.clone(), + withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, stake_pool.withdraw_bump_seed, validator_stake_account_info.clone(), @@ -1492,7 +1557,7 @@ impl Processor { token_program_info.clone(), pool_mint_info.clone(), dest_user_info.clone(), - withdraw_info.clone(), + withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, stake_pool.withdraw_bump_seed, new_pool_tokens, @@ -1530,7 +1595,7 @@ impl Processor { let account_info_iter = &mut accounts.iter(); let stake_pool_info = next_account_info(account_info_iter)?; let validator_list_info = next_account_info(account_info_iter)?; - let withdraw_info = next_account_info(account_info_iter)?; + let withdraw_authority_info = next_account_info(account_info_iter)?; let stake_split_from = next_account_info(account_info_iter)?; let stake_split_to = next_account_info(account_info_iter)?; let user_stake_authority = next_account_info(account_info_iter)?; @@ -1550,8 +1615,12 @@ impl Processor { return Err(StakePoolError::InvalidState.into()); } - stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?; stake_pool.check_mint(pool_mint_info)?; + stake_pool.check_authority_withdraw( + withdraw_authority_info.key, + program_id, + stake_pool_info.key, + )?; if stake_pool.token_program_id != *token_program_info.key { return Err(ProgramError::IncorrectProgramId); @@ -1601,7 +1670,7 @@ impl Processor { token_program_info.clone(), burn_from_info.clone(), pool_mint_info.clone(), - withdraw_info.clone(), + withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, stake_pool.withdraw_bump_seed, pool_tokens, @@ -1610,33 +1679,20 @@ impl Processor { Self::stake_split( stake_pool_info.key, stake_split_from.clone(), - withdraw_info.clone(), + withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, stake_pool.withdraw_bump_seed, withdraw_lamports, stake_split_to.clone(), )?; - Self::stake_authorize( + Self::stake_authorize_signed( stake_pool_info.key, stake_split_to.clone(), - withdraw_info.clone(), + withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, stake_pool.withdraw_bump_seed, user_stake_authority.key, - stake_program::StakeAuthorize::Withdrawer, - clock_info.clone(), - stake_program_info.clone(), - )?; - - Self::stake_authorize( - stake_pool_info.key, - stake_split_to.clone(), - withdraw_info.clone(), - AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, - user_stake_authority.key, - stake_program::StakeAuthorize::Staker, clock_info.clone(), stake_program_info.clone(), )?; diff --git a/stake-pool/program/src/state.rs b/stake-pool/program/src/state.rs index 94f1e317..31d8e9b2 100644 --- a/stake-pool/program/src/state.rs +++ b/stake-pool/program/src/state.rs @@ -39,9 +39,16 @@ pub struct StakePool { /// distribution pub staker: Pubkey, - /// Deposit authority bump seed - /// for `create_program_address(&[state::StakePool account, "deposit"])` - pub deposit_bump_seed: u8, + /// Deposit authority + /// + /// If a depositor pubkey is specified on initialization, then deposits must be + /// signed by this authority. If no deposit authority is specified, + /// then the stake pool will default to the result of: + /// `Pubkey::find_program_address( + /// &[&stake_pool_address.to_bytes()[..32], b"deposit"], + /// program_id, + /// )` + pub deposit_authority: Pubkey, /// Withdrawal authority bump seed /// for `create_program_address(&[state::StakePool account, "withdrawal"])` @@ -165,19 +172,15 @@ impl StakePool { ) } /// Checks that the deposit authority is valid - pub(crate) fn check_authority_deposit( + pub(crate) fn check_deposit_authority( &self, deposit_authority: &Pubkey, - program_id: &Pubkey, - stake_pool_address: &Pubkey, ) -> Result<(), ProgramError> { - Self::check_authority( - deposit_authority, - program_id, - stake_pool_address, - crate::AUTHORITY_DEPOSIT, - self.deposit_bump_seed, - ) + if self.deposit_authority == *deposit_authority { + Ok(()) + } else { + Err(StakePoolError::InvalidProgramAddress.into()) + } } /// Check staker validity and signature diff --git a/stake-pool/program/tests/decrease.rs b/stake-pool/program/tests/decrease.rs index c1a31fce..73c63133 100644 --- a/stake-pool/program/tests/decrease.rs +++ b/stake-pool/program/tests/decrease.rs @@ -233,10 +233,7 @@ async fn fail_with_unknown_validator() { decrease_lamports, ) = setup().await; - let unknown_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let unknown_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); unknown_stake .create_and_delegate( &mut banks_client, diff --git a/stake-pool/program/tests/deposit.rs b/stake-pool/program/tests/deposit.rs index a202e8d0..d19a9144 100644 --- a/stake-pool/program/tests/deposit.rs +++ b/stake-pool/program/tests/deposit.rs @@ -102,28 +102,6 @@ async fn test_stake_pool_deposit() { ) .await; - // Change authority to the stake pool's deposit - authorize_stake_account( - &mut banks_client, - &payer, - &recent_blockhash, - &user_stake.pubkey(), - &stake_authority, - &stake_pool_accounts.deposit_authority, - stake_program::StakeAuthorize::Withdrawer, - ) - .await; - authorize_stake_account( - &mut banks_client, - &payer, - &recent_blockhash, - &user_stake.pubkey(), - &stake_authority, - &stake_pool_accounts.deposit_authority, - stake_program::StakeAuthorize::Staker, - ) - .await; - // make pool token account let user_pool_account = Keypair::new(); create_token_account( @@ -163,6 +141,7 @@ async fn test_stake_pool_deposit() { &user_stake.pubkey(), &user_pool_account.pubkey(), &validator_stake_account.stake_account, + &stake_authority, ) .await .unwrap(); @@ -293,8 +272,8 @@ async fn test_stake_pool_deposit_with_wrong_token_program_id() { let user_stake = Keypair::new(); let lockup = stake_program::Lockup::default(); let authorized = stake_program::Authorized { - staker: stake_pool_accounts.deposit_authority, - withdrawer: stake_pool_accounts.deposit_authority, + staker: user.pubkey(), + withdrawer: user.pubkey(), }; create_independent_stake_account( &mut banks_client, @@ -323,22 +302,21 @@ async fn test_stake_pool_deposit_with_wrong_token_program_id() { let wrong_token_program = Keypair::new(); let mut transaction = Transaction::new_with_payer( - &[instruction::deposit( + &instruction::deposit( &id(), &stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.validator_list.pubkey(), - &stake_pool_accounts.deposit_authority, &stake_pool_accounts.withdraw_authority, &user_stake.pubkey(), + &user.pubkey(), &validator_stake_account.stake_account, &user_pool_account.pubkey(), &stake_pool_accounts.pool_mint.pubkey(), &wrong_token_program.pubkey(), - ) - .unwrap()], + ), Some(&payer.pubkey()), ); - transaction.sign(&[&payer], recent_blockhash); + transaction.sign(&[&payer, &user], recent_blockhash); let transaction_error = banks_client .process_transaction(transaction) .await @@ -368,8 +346,8 @@ async fn test_stake_pool_deposit_with_wrong_validator_list_account() { let user_stake = Keypair::new(); let lockup = stake_program::Lockup::default(); let authorized = stake_program::Authorized { - staker: stake_pool_accounts.deposit_authority, - withdrawer: stake_pool_accounts.deposit_authority, + staker: user.pubkey(), + withdrawer: user.pubkey(), }; create_independent_stake_account( &mut banks_client, @@ -406,6 +384,7 @@ async fn test_stake_pool_deposit_with_wrong_validator_list_account() { &user_stake.pubkey(), &user_pool_account.pubkey(), &validator_stake_account.stake_account, + &user, ) .await .err() @@ -432,10 +411,8 @@ async fn test_stake_pool_deposit_to_unknown_validator() { .await .unwrap(); - let validator_stake_account = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let validator_stake_account = + ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); validator_stake_account .create_and_delegate( &mut banks_client, @@ -462,8 +439,8 @@ async fn test_stake_pool_deposit_to_unknown_validator() { let user_stake = Keypair::new(); let lockup = stake_program::Lockup::default(); let authorized = stake_program::Authorized { - staker: stake_pool_accounts.deposit_authority, - withdrawer: stake_pool_accounts.deposit_authority, + staker: user.pubkey(), + withdrawer: user.pubkey(), }; create_independent_stake_account( &mut banks_client, @@ -484,6 +461,7 @@ async fn test_stake_pool_deposit_to_unknown_validator() { &user_stake.pubkey(), &user_pool_account.pubkey(), &validator_stake_account.stake_account, + &user, ) .await .err() @@ -503,75 +481,6 @@ async fn test_stake_pool_deposit_to_unknown_validator() { } } -#[tokio::test] -async fn test_stake_pool_deposit_with_wrong_deposit_authority() { - let ( - mut banks_client, - payer, - recent_blockhash, - mut stake_pool_accounts, - validator_stake_account, - ) = setup().await; - - let user = Keypair::new(); - // make stake account - let user_stake = Keypair::new(); - let lockup = stake_program::Lockup::default(); - let authorized = stake_program::Authorized { - staker: stake_pool_accounts.deposit_authority, - withdrawer: stake_pool_accounts.deposit_authority, - }; - create_independent_stake_account( - &mut banks_client, - &payer, - &recent_blockhash, - &user_stake, - &authorized, - &lockup, - TEST_STAKE_AMOUNT, - ) - .await; - - // make pool token account - let user_pool_account = Keypair::new(); - create_token_account( - &mut banks_client, - &payer, - &recent_blockhash, - &user_pool_account, - &stake_pool_accounts.pool_mint.pubkey(), - &user.pubkey(), - ) - .await - .unwrap(); - - stake_pool_accounts.deposit_authority = Keypair::new().pubkey(); - - let transaction_error = stake_pool_accounts - .deposit_stake( - &mut banks_client, - &payer, - &recent_blockhash, - &user_stake.pubkey(), - &user_pool_account.pubkey(), - &validator_stake_account.stake_account, - ) - .await - .err() - .unwrap(); - - match transaction_error { - TransportError::TransactionError(TransactionError::InstructionError( - _, - InstructionError::Custom(error_index), - )) => { - let program_error = error::StakePoolError::InvalidProgramAddress as u32; - assert_eq!(error_index, program_error); - } - _ => panic!("Wrong error occurs while try to make a deposit with wrong deposit authority"), - } -} - #[tokio::test] async fn test_stake_pool_deposit_with_wrong_withdraw_authority() { let ( @@ -587,8 +496,8 @@ async fn test_stake_pool_deposit_with_wrong_withdraw_authority() { let user_stake = Keypair::new(); let lockup = stake_program::Lockup::default(); let authorized = stake_program::Authorized { - staker: stake_pool_accounts.deposit_authority, - withdrawer: stake_pool_accounts.deposit_authority, + staker: user.pubkey(), + withdrawer: user.pubkey(), }; create_independent_stake_account( &mut banks_client, @@ -624,6 +533,7 @@ async fn test_stake_pool_deposit_with_wrong_withdraw_authority() { &user_stake.pubkey(), &user_pool_account.pubkey(), &validator_stake_account.stake_account, + &user, ) .await .err() @@ -641,76 +551,18 @@ async fn test_stake_pool_deposit_with_wrong_withdraw_authority() { } } -#[tokio::test] -async fn test_stake_pool_deposit_with_wrong_set_deposit_authority() { - let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) = - setup().await; - - let user = Keypair::new(); - // make stake account - let user_stake = Keypair::new(); - let lockup = stake_program::Lockup::default(); - let authorized = stake_program::Authorized { - staker: Keypair::new().pubkey(), - withdrawer: stake_pool_accounts.deposit_authority, - }; - create_independent_stake_account( - &mut banks_client, - &payer, - &recent_blockhash, - &user_stake, - &authorized, - &lockup, - TEST_STAKE_AMOUNT, - ) - .await; - // make pool token account - let user_pool_account = Keypair::new(); - create_token_account( - &mut banks_client, - &payer, - &recent_blockhash, - &user_pool_account, - &stake_pool_accounts.pool_mint.pubkey(), - &user.pubkey(), - ) - .await - .unwrap(); - - let transaction_error = stake_pool_accounts - .deposit_stake( - &mut banks_client, - &payer, - &recent_blockhash, - &user_stake.pubkey(), - &user_pool_account.pubkey(), - &validator_stake_account.stake_account, - ) - .await - .err() - .unwrap(); - - match transaction_error { - TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { - assert_eq!(error, InstructionError::MissingRequiredSignature); - } - _ => { - panic!("Wrong error occurs while try to make deposit with wrong set deposit authority") - } - } -} - #[tokio::test] async fn test_stake_pool_deposit_with_wrong_mint_for_receiver_acc() { let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) = setup().await; // make stake account + let user = Keypair::new(); let user_stake = Keypair::new(); let lockup = stake_program::Lockup::default(); let authorized = stake_program::Authorized { - staker: stake_pool_accounts.deposit_authority, - withdrawer: stake_pool_accounts.deposit_authority, + staker: user.pubkey(), + withdrawer: user.pubkey(), }; create_independent_stake_account( &mut banks_client, @@ -757,6 +609,7 @@ async fn test_stake_pool_deposit_with_wrong_mint_for_receiver_acc() { &user_stake.pubkey(), &outside_pool_fee_acc.pubkey(), &validator_stake_account.stake_account, + &user, ) .await .err() @@ -779,3 +632,180 @@ async fn test_deposit_with_uninitialized_validator_list() {} // TODO #[tokio::test] async fn test_deposit_with_out_of_dated_pool_balances() {} // TODO + +#[tokio::test] +async fn success_with_deposit_authority() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let deposit_authority = Keypair::new(); + let stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) + .await + .unwrap(); + + let validator_stake_account = simple_add_validator_to_pool( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts, + ) + .await; + + let user = Keypair::new(); + let user_stake = Keypair::new(); + let lockup = stake_program::Lockup::default(); + let authorized = stake_program::Authorized { + staker: user.pubkey(), + withdrawer: user.pubkey(), + }; + let _stake_lamports = create_independent_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake, + &authorized, + &lockup, + TEST_STAKE_AMOUNT, + ) + .await; + + create_vote( + &mut banks_client, + &payer, + &recent_blockhash, + &validator_stake_account.validator, + &validator_stake_account.vote, + ) + .await; + delegate_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user, + &validator_stake_account.vote.pubkey(), + ) + .await; + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + &user, + ) + .await + .unwrap(); +} + +#[tokio::test] +async fn fail_without_deposit_authority_signature() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let deposit_authority = Keypair::new(); + let mut stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) + .await + .unwrap(); + + let validator_stake_account = simple_add_validator_to_pool( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts, + ) + .await; + + let user = Keypair::new(); + let user_stake = Keypair::new(); + let lockup = stake_program::Lockup::default(); + let authorized = stake_program::Authorized { + staker: user.pubkey(), + withdrawer: user.pubkey(), + }; + let _stake_lamports = create_independent_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake, + &authorized, + &lockup, + TEST_STAKE_AMOUNT, + ) + .await; + + create_vote( + &mut banks_client, + &payer, + &recent_blockhash, + &validator_stake_account.validator, + &validator_stake_account.vote, + ) + .await; + delegate_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user, + &validator_stake_account.vote.pubkey(), + ) + .await; + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let wrong_depositor = Keypair::new(); + stake_pool_accounts.deposit_authority = wrong_depositor.pubkey(); + stake_pool_accounts.deposit_authority_keypair = Some(wrong_depositor); + + let error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + &user, + ) + .await + .unwrap_err() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + assert_eq!( + error_index, + error::StakePoolError::InvalidProgramAddress as u32 + ); + } + _ => panic!("Wrong error occurs while try to make a deposit with wrong stake program ID"), + } +} diff --git a/stake-pool/program/tests/helpers/mod.rs b/stake-pool/program/tests/helpers/mod.rs index 6bd6c769..1772a13e 100644 --- a/stake-pool/program/tests/helpers/mod.rs +++ b/stake-pool/program/tests/helpers/mod.rs @@ -210,6 +210,7 @@ pub async fn create_stake_pool( pool_token_account: &Pubkey, manager: &Keypair, staker: &Pubkey, + deposit_authority: &Option, fee: &state::Fee, max_validators: u32, ) -> Result<(), TransportError> { @@ -245,6 +246,7 @@ pub async fn create_stake_pool( pool_mint, pool_token_account, &spl_token::id(), + deposit_authority.as_ref().map(|k| k.pubkey()), *fee, max_validators, ) @@ -252,10 +254,11 @@ pub async fn create_stake_pool( ], Some(&payer.pubkey()), ); - transaction.sign( - &[payer, stake_pool, validator_list, manager], - *recent_blockhash, - ); + let mut signers = vec![payer, stake_pool, validator_list, manager]; + if let Some(deposit_authority) = deposit_authority.as_ref() { + signers.push(deposit_authority); + } + transaction.sign(&signers, *recent_blockhash); banks_client.process_transaction(transaction).await?; Ok(()) } @@ -424,14 +427,13 @@ pub async fn authorize_stake_account( pub struct ValidatorStakeAccount { pub stake_account: Pubkey, pub transient_stake_account: Pubkey, - pub target_authority: Pubkey, pub vote: Keypair, pub validator: Keypair, pub stake_pool: Pubkey, } impl ValidatorStakeAccount { - pub fn new_with_target_authority(authority: &Pubkey, stake_pool: &Pubkey) -> Self { + pub fn new(stake_pool: &Pubkey) -> Self { let validator = Keypair::new(); let vote = Keypair::new(); let (stake_account, _) = find_stake_program_address(&id(), &vote.pubkey(), stake_pool); @@ -440,7 +442,6 @@ impl ValidatorStakeAccount { ValidatorStakeAccount { stake_account, transient_stake_account, - target_authority: *authority, vote, validator, stake_pool: *stake_pool, @@ -473,28 +474,6 @@ impl ValidatorStakeAccount { &self.vote.pubkey(), ) .await; - - authorize_stake_account( - &mut banks_client, - &payer, - &recent_blockhash, - &self.stake_account, - &staker, - &self.target_authority, - stake_program::StakeAuthorize::Staker, - ) - .await; - - authorize_stake_account( - &mut banks_client, - &payer, - &recent_blockhash, - &self.stake_account, - &staker, - &self.target_authority, - stake_program::StakeAuthorize::Withdrawer, - ) - .await; } } @@ -508,6 +487,7 @@ pub struct StakePoolAccounts { pub staker: Keypair, pub withdraw_authority: Pubkey, pub deposit_authority: Pubkey, + pub deposit_authority_keypair: Option, pub fee: state::Fee, pub max_validators: u32, } @@ -541,6 +521,7 @@ impl StakePoolAccounts { staker, withdraw_authority, deposit_authority, + deposit_authority_keypair: None, fee: state::Fee { numerator: 1, denominator: 100, @@ -549,6 +530,13 @@ impl StakePoolAccounts { } } + pub fn new_with_deposit_authority(deposit_authority: Keypair) -> Self { + let mut stake_pool_accounts = Self::new(); + stake_pool_accounts.deposit_authority = deposit_authority.pubkey(); + stake_pool_accounts.deposit_authority_keypair = Some(deposit_authority); + stake_pool_accounts + } + pub fn calculate_fee(&self, amount: u64) -> u64 { amount * self.fee.numerator / self.fee.denominator } @@ -601,6 +589,7 @@ impl StakePoolAccounts { &self.pool_fee_account.pubkey(), &self.manager, &self.staker.pubkey(), + &self.deposit_authority_keypair, &self.fee, self.max_validators, ) @@ -608,6 +597,7 @@ impl StakePoolAccounts { Ok(()) } + #[allow(clippy::too_many_arguments)] pub async fn deposit_stake( &self, banks_client: &mut BanksClient, @@ -616,23 +606,43 @@ impl StakePoolAccounts { stake: &Pubkey, pool_account: &Pubkey, validator_stake_account: &Pubkey, + current_staker: &Keypair, ) -> Result<(), TransportError> { - let transaction = Transaction::new_signed_with_payer( - &[instruction::deposit( + let mut signers = vec![payer, current_staker]; + let instructions = if let Some(deposit_authority) = self.deposit_authority_keypair.as_ref() + { + signers.push(deposit_authority); + instruction::deposit_with_authority( &id(), &self.stake_pool.pubkey(), &self.validator_list.pubkey(), &self.deposit_authority, &self.withdraw_authority, stake, + ¤t_staker.pubkey(), validator_stake_account, pool_account, &self.pool_mint.pubkey(), &spl_token::id(), ) - .unwrap()], + } else { + instruction::deposit( + &id(), + &self.stake_pool.pubkey(), + &self.validator_list.pubkey(), + &self.withdraw_authority, + stake, + ¤t_staker.pubkey(), + validator_stake_account, + pool_account, + &self.pool_mint.pubkey(), + &spl_token::id(), + ) + }; + let transaction = Transaction::new_signed_with_payer( + &instructions, Some(&payer.pubkey()), - &[payer], + &signers, *recent_blockhash, ); banks_client.process_transaction(transaction).await?; @@ -771,7 +781,6 @@ impl StakePoolAccounts { &id(), &self.stake_pool.pubkey(), &self.staker.pubkey(), - &self.deposit_authority, &self.withdraw_authority, &self.validator_list.pubkey(), stake, @@ -874,10 +883,7 @@ pub async fn simple_add_validator_to_pool( recent_blockhash: &Hash, stake_pool_accounts: &StakePoolAccounts, ) -> ValidatorStakeAccount { - let validator_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let validator_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); validator_stake .create_and_delegate( banks_client, @@ -970,26 +976,6 @@ impl DepositStakeAccount { recent_blockhash: &Hash, stake_pool_accounts: &StakePoolAccounts, ) { - authorize_stake_account( - banks_client, - payer, - recent_blockhash, - &self.stake.pubkey(), - &self.authority, - &stake_pool_accounts.deposit_authority, - stake_program::StakeAuthorize::Staker, - ) - .await; - authorize_stake_account( - banks_client, - &payer, - &recent_blockhash, - &self.stake.pubkey(), - &self.authority, - &stake_pool_accounts.deposit_authority, - stake_program::StakeAuthorize::Withdrawer, - ) - .await; // make pool token account create_token_account( banks_client, @@ -1010,6 +996,7 @@ impl DepositStakeAccount { &self.stake.pubkey(), &self.pool_account.pubkey(), &self.validator_stake_account, + &self.authority, ) .await .unwrap(); @@ -1052,26 +1039,6 @@ pub async fn simple_deposit( &vote_account, ) .await; - authorize_stake_account( - banks_client, - payer, - recent_blockhash, - &stake.pubkey(), - &authority, - &stake_pool_accounts.deposit_authority, - stake_program::StakeAuthorize::Staker, - ) - .await; - authorize_stake_account( - banks_client, - &payer, - &recent_blockhash, - &stake.pubkey(), - &authority, - &stake_pool_accounts.deposit_authority, - stake_program::StakeAuthorize::Withdrawer, - ) - .await; // make pool token account let pool_account = Keypair::new(); create_token_account( @@ -1094,6 +1061,7 @@ pub async fn simple_deposit( &stake.pubkey(), &pool_account.pubkey(), &validator_stake_account, + &authority, ) .await .unwrap(); diff --git a/stake-pool/program/tests/increase.rs b/stake-pool/program/tests/increase.rs index 3a540348..3e155e73 100644 --- a/stake-pool/program/tests/increase.rs +++ b/stake-pool/program/tests/increase.rs @@ -233,10 +233,7 @@ async fn fail_with_unknown_validator() { reserve_lamports, ) = setup().await; - let unknown_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let unknown_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); unknown_stake .create_and_delegate( &mut banks_client, diff --git a/stake-pool/program/tests/initialize.rs b/stake-pool/program/tests/initialize.rs index 44d47a2d..3539a327 100644 --- a/stake-pool/program/tests/initialize.rs +++ b/stake-pool/program/tests/initialize.rs @@ -3,7 +3,7 @@ mod helpers; use { - borsh::BorshSerialize, + borsh::{BorshDeserialize, BorshSerialize}, helpers::*, solana_program::{ borsh::get_packed_len, @@ -226,6 +226,7 @@ async fn fail_with_wrong_max_validators() { &stake_pool_accounts.pool_mint.pubkey(), &stake_pool_accounts.pool_fee_account.pubkey(), &spl_token::id(), + None, stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -296,6 +297,7 @@ async fn fail_with_wrong_mint_authority() { &stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.manager, &stake_pool_accounts.staker.pubkey(), + &None, &stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -384,6 +386,7 @@ async fn fail_with_wrong_token_program_id() { &stake_pool_accounts.pool_mint.pubkey(), &stake_pool_accounts.pool_fee_account.pubkey(), &wrong_token_program.pubkey(), + None, stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -460,6 +463,7 @@ async fn fail_with_wrong_fee_account() { &stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.manager, &stake_pool_accounts.staker.pubkey(), + &None, &stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -547,6 +551,7 @@ async fn fail_with_not_rent_exempt_pool() { &stake_pool_accounts.pool_mint.pubkey(), &stake_pool_accounts.pool_fee_account.pubkey(), &spl_token::id(), + None, stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -621,6 +626,7 @@ async fn fail_with_not_rent_exempt_validator_list() { &stake_pool_accounts.pool_mint.pubkey(), &stake_pool_accounts.pool_fee_account.pubkey(), &spl_token::id(), + None, stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -793,6 +799,7 @@ async fn fail_with_pre_minted_pool_tokens() { &stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.manager, &stake_pool_accounts.staker.pubkey(), + &None, &stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -853,6 +860,7 @@ async fn fail_with_bad_reserve() { &stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.manager, &stake_pool_accounts.staker.pubkey(), + &None, &stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -897,6 +905,7 @@ async fn fail_with_bad_reserve() { &stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.manager, &stake_pool_accounts.staker.pubkey(), + &None, &stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -944,6 +953,7 @@ async fn fail_with_bad_reserve() { &stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.manager, &stake_pool_accounts.staker.pubkey(), + &None, &stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -991,6 +1001,7 @@ async fn fail_with_bad_reserve() { &stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.manager, &stake_pool_accounts.staker.pubkey(), + &None, &stake_pool_accounts.fee, stake_pool_accounts.max_validators, ) @@ -1008,3 +1019,23 @@ async fn fail_with_bad_reserve() { ); } } + +#[tokio::test] +async fn success_with_required_deposit_authority() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let deposit_authority = Keypair::new(); + let stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) + .await + .unwrap(); + + // Stake pool now exists + let stake_pool_account = + get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; + let stake_pool = state::StakePool::try_from_slice(stake_pool_account.data.as_slice()).unwrap(); + assert_eq!( + stake_pool.deposit_authority, + stake_pool_accounts.deposit_authority + ); +} diff --git a/stake-pool/program/tests/update_validator_list_balance.rs b/stake-pool/program/tests/update_validator_list_balance.rs index 460b72ac..cb011408 100644 --- a/stake-pool/program/tests/update_validator_list_balance.rs +++ b/stake-pool/program/tests/update_validator_list_balance.rs @@ -66,10 +66,7 @@ async fn setup( let mut stake_accounts: Vec = vec![]; let mut deposit_accounts: Vec = vec![]; for _ in 0..num_validators { - let stake_account = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let stake_account = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); stake_account .create_and_delegate( &mut context.banks_client, diff --git a/stake-pool/program/tests/vsa_add.rs b/stake-pool/program/tests/vsa_add.rs index 535b6df7..d577480d 100644 --- a/stake-pool/program/tests/vsa_add.rs +++ b/stake-pool/program/tests/vsa_add.rs @@ -38,10 +38,7 @@ async fn setup() -> ( .await .unwrap(); - let user_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); user_stake .create_and_delegate( &mut banks_client, @@ -126,7 +123,6 @@ async fn fail_with_wrong_validator_list_account() { &id(), &stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.staker.pubkey(), - &stake_pool_accounts.deposit_authority, &stake_pool_accounts.withdraw_authority, &wrong_validator_list.pubkey(), &user_stake.stake_account, @@ -162,10 +158,7 @@ async fn fail_too_little_stake() { .await .unwrap(); - let user_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); create_vote( &mut banks_client, &payer, @@ -203,28 +196,6 @@ async fn fail_too_little_stake() { banks_client.process_transaction(transaction).await.unwrap(); - authorize_stake_account( - &mut banks_client, - &payer, - &recent_blockhash, - &user_stake.stake_account, - &stake_pool_accounts.staker, - &user_stake.target_authority, - stake_program::StakeAuthorize::Staker, - ) - .await; - - authorize_stake_account( - &mut banks_client, - &payer, - &recent_blockhash, - &user_stake.stake_account, - &stake_pool_accounts.staker, - &user_stake.target_authority, - stake_program::StakeAuthorize::Withdrawer, - ) - .await; - let error = stake_pool_accounts .add_validator_to_pool( &mut banks_client, @@ -253,10 +224,7 @@ async fn fail_too_much_stake() { .await .unwrap(); - let user_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); user_stake .create_and_delegate( &mut banks_client, @@ -344,7 +312,6 @@ async fn fail_wrong_staker() { &id(), &stake_pool_accounts.stake_pool.pubkey(), &malicious.pubkey(), - &stake_pool_accounts.deposit_authority, &stake_pool_accounts.withdraw_authority, &stake_pool_accounts.validator_list.pubkey(), &user_stake.stake_account, @@ -379,7 +346,6 @@ async fn fail_without_signature() { let accounts = vec![ AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), false), - AccountMeta::new_readonly(stake_pool_accounts.deposit_authority, false), AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false), AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false), AccountMeta::new(user_stake.stake_account, false), @@ -425,7 +391,6 @@ async fn fail_with_wrong_stake_program_id() { let accounts = vec![ AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), true), - AccountMeta::new_readonly(stake_pool_accounts.deposit_authority, false), AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false), AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false), AccountMeta::new(user_stake.stake_account, false), @@ -468,10 +433,7 @@ async fn fail_add_too_many_validator_stake_accounts() { .await .unwrap(); - let user_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); user_stake .create_and_delegate( &mut banks_client, @@ -491,10 +453,7 @@ async fn fail_add_too_many_validator_stake_accounts() { .await; assert!(error.is_none()); - let user_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); user_stake .create_and_delegate( &mut banks_client, diff --git a/stake-pool/program/tests/vsa_remove.rs b/stake-pool/program/tests/vsa_remove.rs index 8f89ef95..161d432d 100644 --- a/stake-pool/program/tests/vsa_remove.rs +++ b/stake-pool/program/tests/vsa_remove.rs @@ -38,10 +38,7 @@ async fn setup() -> ( .await .unwrap(); - let user_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); user_stake .create_and_delegate( &mut banks_client, diff --git a/stake-pool/program/tests/withdraw.rs b/stake-pool/program/tests/withdraw.rs index 6250fd1a..a2233b58 100644 --- a/stake-pool/program/tests/withdraw.rs +++ b/stake-pool/program/tests/withdraw.rs @@ -403,10 +403,8 @@ async fn fail_with_unknown_validator() { .await .unwrap(); - let validator_stake_account = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let validator_stake_account = + ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); validator_stake_account .create_and_delegate( &mut banks_client, @@ -416,10 +414,7 @@ async fn fail_with_unknown_validator() { ) .await; - let user_stake = ValidatorStakeAccount::new_with_target_authority( - &stake_pool_accounts.deposit_authority, - &stake_pool_accounts.stake_pool.pubkey(), - ); + let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); user_stake .create_and_delegate( &mut banks_client,