diff --git a/stake-pool/cli/src/main.rs b/stake-pool/cli/src/main.rs index d1c05aad26e..fee1c4aa24c 100755 --- a/stake-pool/cli/src/main.rs +++ b/stake-pool/cli/src/main.rs @@ -2480,7 +2480,7 @@ fn command_check_existing_validators() -> CommandResult { } if !invalid_validators.is_empty() { - let mut message: String = "".to_string(); + let mut message: String = "Invalid validators: \n".to_string(); for (vote_account_pubkey, validator_comparable_parameters) in invalid_validators.into_iter() { message = message + format!("{} : {:?}", vote_account_pubkey.to_string(), validator_comparable_parameters).as_str() + "\n"; @@ -2492,6 +2492,40 @@ fn command_check_existing_validators() -> CommandResult { Ok(()) } +fn command_change_structure( + config: &Config, + stake_pool_address: &Pubkey, +) -> CommandResult { + if !config.no_update { + command_update(config, stake_pool_address, false, false)?; + } + + let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; + + let mut signers = vec![ + config.fee_payer.as_ref(), + config.manager.as_ref() + ]; + + let instructions = vec![ + spl_stake_pool::instruction::change_structure( + &spl_stake_pool::id(), + stake_pool_address, + &config.manager.pubkey(), + ) + ]; + + let mut transaction = + Transaction::new_with_payer(&instructions, Some(&config.fee_payer.pubkey())); + + let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; + check_fee_payer_balance(config, fee_calculator.calculate_fee(transaction.message()))?; + unique_signers!(signers); + transaction.sign(&signers, recent_blockhash); + send_transaction(config, transaction)?; + Ok(()) +} + fn main() { solana_logger::setup_with_default("solana=info"); @@ -3501,6 +3535,18 @@ fn main() { .subcommand(SubCommand::with_name("check-existing-validators") .about("Check existing in stake pool validator`s list validators") ) + .subcommand(SubCommand::with_name("change-structure") + .about("Change old StakePool for new") + .arg( + Arg::with_name("pool") + .index(1) + .validator(is_pubkey) + .value_name("POOL_ADDRESS") + .takes_value(true) + .required(true) + .help("Stake pool address."), + ) + ) .get_matches(); let mut wallet_manager = None; @@ -3962,6 +4008,13 @@ fn main() { ("check-existing-validators", Some(_arg_matches)) => { command_check_existing_validators() } + ("change-structure", Some(arg_matches)) => { + let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); + command_change_structure( + &config, + &stake_pool_address + ) + } _ => unreachable!(), } .map_err(|err| { diff --git a/stake-pool/program/src/instruction.rs b/stake-pool/program/src/instruction.rs index 70bb92bc24c..ea7ad83419d 100755 --- a/stake-pool/program/src/instruction.rs +++ b/stake-pool/program/src/instruction.rs @@ -421,6 +421,15 @@ pub enum StakePoolInstruction { /// 7. `[]` Stake program account /// 8. `[s]` (Optional) Stake pool sol withdraw authority WithdrawLiquiditySol(u64), + + + + + /// DELETE AFTER EXECUTION + /// + /// 0. `[w]` Stake pool + /// 1. `[s]` Manager + ChangeStructure, } /// Creates an 'initialize' instruction. @@ -1478,4 +1487,23 @@ pub fn withdraw_liquidity_sol_with_authority( .try_to_vec() .unwrap(), } +} + +/// DELETE +pub fn change_structure( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, +) -> Instruction { + let accounts = vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new_readonly(*manager, true), + ]; + Instruction { + program_id: *program_id, + accounts, + data: StakePoolInstruction::ChangeStructure + .try_to_vec() + .unwrap(), + } } \ No newline at end of file diff --git a/stake-pool/program/src/processor.rs b/stake-pool/program/src/processor.rs index 92da7455196..735d033034c 100755 --- a/stake-pool/program/src/processor.rs +++ b/stake-pool/program/src/processor.rs @@ -1,5 +1,7 @@ //! Program state processor +use crate::state::StakePoolChanged; + use { crate::{ error::StakePoolError, @@ -18,6 +20,7 @@ use { account_info::next_account_info, account_info::AccountInfo, borsh::try_from_slice_unchecked, + borsh::get_packed_len, clock::{Clock, Epoch}, decode_error::DecodeError, entrypoint::ProgramResult, @@ -3007,6 +3010,76 @@ impl Processor { Ok(()) } + /// DELETE + #[inline(never)] // needed to avoid stack size violation + fn process_change_structure( + program_id: &Pubkey, + accounts: &[AccountInfo], + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let stake_pool_info = next_account_info(account_info_iter)?; + let manager_info = next_account_info(account_info_iter)?; + + check_account_owner(stake_pool_info, program_id)?; + let stake_pool = try_from_slice_unchecked::(&stake_pool_info.data.borrow())?; + if !stake_pool.is_valid() { + return Err(StakePoolError::InvalidState.into()); + } + + if stake_pool.last_update_epoch < Clock::get()?.epoch { + return Err(StakePoolError::StakeListAndPoolOutOfDate.into()); + } + + stake_pool.check_manager(manager_info)?; + + if get_packed_len::() < get_packed_len::() { + msg!("New structure size is bigger"); + + return Err(StakePoolError::InvalidState.into()); + } + + let stake_pool_changed = StakePoolChanged { + account_type: stake_pool.account_type, + manager: stake_pool.manager, + staker: stake_pool.staker, + stake_deposit_authority: stake_pool.stake_deposit_authority, + stake_withdraw_bump_seed: stake_pool.stake_withdraw_bump_seed, + validator_list: stake_pool.validator_list, + reserve_stake: stake_pool.reserve_stake, + pool_mint: stake_pool.pool_mint, + manager_fee_account: stake_pool.manager_fee_account, + token_program_id: stake_pool.token_program_id, + total_lamports: stake_pool.total_lamports, + pool_token_supply: stake_pool.pool_token_supply, + last_update_epoch: stake_pool.last_update_epoch, + lockup: stake_pool.lockup, + epoch_fee: stake_pool.epoch_fee, + next_epoch_fee: stake_pool.next_epoch_fee, + preferred_deposit_validator_vote_address: stake_pool.preferred_deposit_validator_vote_address, + preferred_withdraw_validator_vote_address: stake_pool.preferred_withdraw_validator_vote_address, + stake_deposit_fee: stake_pool.stake_deposit_fee, + stake_withdrawal_fee: stake_pool.stake_withdrawal_fee, + next_stake_withdrawal_fee: stake_pool.next_stake_withdrawal_fee, + stake_referral_fee: stake_pool.stake_referral_fee, + sol_deposit_authority: stake_pool.sol_deposit_authority, + sol_deposit_fee: stake_pool.sol_deposit_fee, + sol_referral_fee: stake_pool.sol_referral_fee, + sol_withdraw_authority: stake_pool.sol_withdraw_authority, + sol_withdrawal_fee: stake_pool.sol_withdrawal_fee, + next_sol_withdrawal_fee: stake_pool.next_sol_withdrawal_fee, + last_epoch_pool_token_supply: stake_pool.last_epoch_pool_token_supply, + last_epoch_total_lamports: stake_pool.last_epoch_total_lamports, + rate_of_exchange: stake_pool.rate_of_exchange, + treasury_fee_account: stake_pool.treasury_fee_account, + treasury_fee: stake_pool.treasury_fee, + total_lamports_liquidity: 100121 // TODO TEST !!!!!!!!!!!!!!!!!!! 0 + }; + + stake_pool_changed.serialize(&mut *stake_pool_info.data.borrow_mut())?; + + Ok(()) + } + /// Processes [Instruction](enum.Instruction.html). pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult { let instruction = StakePoolInstruction::try_from_slice(input)?; @@ -3144,6 +3217,10 @@ impl Processor { msg!("Instruction: WithdrawLiquiditySol"); Self::process_withdraw_liquidity_sol(program_id, accounts, lamports) } + StakePoolInstruction::ChangeStructure => { + msg!("Instruction: ChnageStructure"); + Self::process_change_structure(program_id, accounts) + } } } } diff --git a/stake-pool/program/src/state.rs b/stake-pool/program/src/state.rs index 11402f75403..5fda39f7175 100755 --- a/stake-pool/program/src/state.rs +++ b/stake-pool/program/src/state.rs @@ -39,6 +39,133 @@ impl Default for AccountType { } } +/// Initialized program details. +#[repr(C)] +#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] +pub struct StakePoolChanged { + /// Account type, must be StakePool currently + pub account_type: AccountType, + + /// Manager authority, allows for updating the staker, manager, and fee account + pub manager: Pubkey, + + /// Staker authority, allows for adding and removing validators, and managing stake + /// distribution + pub staker: Pubkey, + + /// Stake 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 stake_deposit_authority: Pubkey, + + /// Stake withdrawal authority bump seed + /// for `create_program_address(&[state::StakePool account, "withdrawal"])` + pub stake_withdraw_bump_seed: u8, + + /// Validator stake list storage account + pub validator_list: Pubkey, + + /// Reserve stake account, holds deactivated stake + pub reserve_stake: Pubkey, + + /// Pool Mint + pub pool_mint: Pubkey, + + /// Manager fee account + pub manager_fee_account: Pubkey, + + /// Pool token program id + pub token_program_id: Pubkey, + + /// Total stake under management. + /// Note that if `last_update_epoch` does not match the current epoch then + /// this field may not be accurate + pub total_lamports: u64, + + /// Total supply of pool tokens (should always match the supply in the Pool Mint) + pub pool_token_supply: u64, + + /// Last epoch the `total_lamports` field was updated + pub last_update_epoch: u64, + + /// Lockup that all stakes in the pool must have + pub lockup: Lockup, + + /// Fee taken as a proportion of rewards each epoch + pub epoch_fee: Fee, + + /// Fee for next epoch + pub next_epoch_fee: Option, + + /// Preferred deposit validator vote account pubkey + pub preferred_deposit_validator_vote_address: Option, + + /// Preferred withdraw validator vote account pubkey + pub preferred_withdraw_validator_vote_address: Option, + + /// Fee assessed on stake deposits + pub stake_deposit_fee: Fee, + + /// Fee assessed on withdrawals + pub stake_withdrawal_fee: Fee, + + /// Future stake withdrawal fee, to be set for the following epoch + pub next_stake_withdrawal_fee: Option, + + /// Fees paid out to referrers on referred stake deposits. + /// Expressed as a percentage (0 - 100) of deposit fees. + /// i.e. `stake_deposit_fee`% of stake deposited is collected as deposit fees for every deposit + /// and `stake_referral_fee`% of the collected stake deposit fees is paid out to the referrer + pub stake_referral_fee: u8, + + /// Toggles whether the `DepositSol` instruction requires a signature from + /// this `sol_deposit_authority` + pub sol_deposit_authority: Option, + + /// Fee assessed on SOL deposits + pub sol_deposit_fee: Fee, + + /// Fees paid out to referrers on referred SOL deposits. + /// Expressed as a percentage (0 - 100) of SOL deposit fees. + /// i.e. `sol_deposit_fee`% of SOL deposited is collected as deposit fees for every deposit + /// and `sol_referral_fee`% of the collected SOL deposit fees is paid out to the referrer + pub sol_referral_fee: u8, + + /// Toggles whether the `WithdrawSol` instruction requires a signature from + /// the `deposit_authority` + pub sol_withdraw_authority: Option, + + /// Fee assessed on SOL withdrawals + pub sol_withdrawal_fee: Fee, + + /// Future SOL withdrawal fee, to be set for the following epoch + pub next_sol_withdrawal_fee: Option, + + /// Last epoch's total pool tokens, used only for APR estimation + pub last_epoch_pool_token_supply: u64, + + /// Last epoch's total lamports, used only for APR estimation + pub last_epoch_total_lamports: u64, + + /// Last epoch's exchange rate for SOL deposit and withdraw + pub rate_of_exchange: Option, + + /// Treasury fee account + pub treasury_fee_account: Pubkey, + + /// Fee assessed on taking rewards for treasury + pub treasury_fee: Fee, + + /// Total liquidity in Sol equivalent under management. + pub total_lamports_liquidity: u64, +} + /// Initialized program details. #[repr(C)] #[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]