diff --git a/stake-pool/cli/scripts/deposit-withdraw.sh b/stake-pool/cli/scripts/deposit-withdraw.sh index f8508135..318cee52 100755 --- a/stake-pool/cli/scripts/deposit-withdraw.sh +++ b/stake-pool/cli/scripts/deposit-withdraw.sh @@ -47,7 +47,7 @@ deposit_stakes () { for validator in $(cat $validator_list) do stake=$(solana-keygen pubkey $keys_dir/stake_$validator.json) - $spl_stake_pool deposit $stake_pool_pubkey $stake + $spl_stake_pool deposit-stake $stake_pool_pubkey $stake done } @@ -57,7 +57,7 @@ withdraw_stakes () { pool_amount=$3 for validator in $(cat $validator_list) do - $spl_stake_pool withdraw $stake_pool_pubkey $pool_amount --vote-account $validator + $spl_stake_pool withdraw-stake $stake_pool_pubkey $pool_amount --vote-account $validator done } diff --git a/stake-pool/cli/src/main.rs b/stake-pool/cli/src/main.rs index fa42aa9c..41386b35 100644 --- a/stake-pool/cli/src/main.rs +++ b/stake-pool/cli/src/main.rs @@ -13,6 +13,7 @@ use { input_parsers::{keypair_of, pubkey_of}, input_validators::{ is_amount, is_keypair, is_keypair_or_ask_keyword, is_parsable, is_pubkey, is_url, + is_valid_percentage, }, keypair::signer_from_path, }, @@ -38,7 +39,7 @@ use { find_withdraw_authority_program_address, instruction::PreferredValidatorType, stake_program::{self, StakeState}, - state::{Fee, StakePool, ValidatorList}, + state::{Fee, FeeType, StakePool, ValidatorList}, }, std::{process::exit, sync::Arc}, }; @@ -49,6 +50,7 @@ struct Config { manager: Box, staker: Box, depositor: Option>, + sol_depositor: Option>, token_owner: Box, fee_payer: Box, dry_run: bool, @@ -153,9 +155,11 @@ fn checked_transaction_with_signers( #[allow(clippy::too_many_arguments)] fn command_create_pool( config: &Config, - deposit_authority: Option, + stake_deposit_authority: Option, fee: Fee, withdrawal_fee: Fee, + stake_deposit_fee: Fee, + stake_referral_fee: u8, max_validators: u32, stake_pool_keypair: Option, mint_keypair: Option, @@ -283,9 +287,11 @@ fn command_create_pool( &mint_keypair.pubkey(), &pool_fee_account, &spl_token::id(), - deposit_authority, + stake_deposit_authority.as_ref().map(|x| x.pubkey()), fee, withdrawal_fee, + stake_deposit_fee, + stake_referral_fee, max_validators, ), ], @@ -311,8 +317,15 @@ fn command_create_pool( &validator_list, config.manager.as_ref(), ]; - unique_signers!(initialize_signers); - initialize_transaction.sign(&initialize_signers, recent_blockhash); + if let Some(stake_deposit_authority) = stake_deposit_authority { + let mut initialize_signers = initialize_signers.clone(); + initialize_signers.push(&stake_deposit_authority); + unique_signers!(initialize_signers); + initialize_transaction.sign(&initialize_signers, recent_blockhash); + } else { + unique_signers!(initialize_signers); + initialize_transaction.sign(&initialize_signers, recent_blockhash); + } send_transaction(config, initialize_transaction)?; Ok(()) } @@ -567,11 +580,12 @@ fn add_associated_token_account( account } -fn command_deposit( +fn command_deposit_stake( config: &Config, stake_pool_address: &Pubkey, stake: &Pubkey, - token_receiver: &Option, + pool_token_receiver_account: &Option, + referrer_token_account: &Option, ) -> CommandResult { if !config.no_update { command_update(config, stake_pool_address, false, false)?; @@ -613,44 +627,50 @@ fn command_deposit( let mut total_rent_free_balances: u64 = 0; // Create token account if not specified - let token_receiver = token_receiver.unwrap_or(add_associated_token_account( - config, - &stake_pool.pool_mint, - &config.token_owner.pubkey(), - &mut instructions, - &mut total_rent_free_balances, - )); + let pool_token_receiver_account = + pool_token_receiver_account.unwrap_or(add_associated_token_account( + config, + &stake_pool.pool_mint, + &config.token_owner.pubkey(), + &mut instructions, + &mut total_rent_free_balances, + )); + + let referrer_token_account = referrer_token_account.unwrap_or(pool_token_receiver_account); let pool_withdraw_authority = find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; - 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 mut deposit_instructions = if let Some(stake_deposit_authority) = config.depositor.as_ref() + { + signers.push(stake_deposit_authority.as_ref()); + if stake_deposit_authority.pubkey() != stake_pool.stake_deposit_authority { let error = format!( "Invalid deposit authority specified, expected {}, received {}", - stake_pool.deposit_authority, - deposit_authority.pubkey() + stake_pool.stake_deposit_authority, + stake_deposit_authority.pubkey() ); return Err(error.into()); } - spl_stake_pool::instruction::deposit_with_authority( + spl_stake_pool::instruction::deposit_stake_with_authority( &spl_stake_pool::id(), stake_pool_address, &stake_pool.validator_list, - &deposit_authority.pubkey(), + &stake_deposit_authority.pubkey(), &pool_withdraw_authority, stake, &config.staker.pubkey(), &validator_stake_account, &stake_pool.reserve_stake, - &token_receiver, + &pool_token_receiver_account, + &stake_pool.manager_fee_account, + &referrer_token_account, &stake_pool.pool_mint, &spl_token::id(), ) } else { - spl_stake_pool::instruction::deposit( + spl_stake_pool::instruction::deposit_stake( &spl_stake_pool::id(), stake_pool_address, &stake_pool.validator_list, @@ -659,7 +679,9 @@ fn command_deposit( &config.staker.pubkey(), &validator_stake_account, &stake_pool.reserve_stake, - &token_receiver, + &pool_token_receiver_account, + &stake_pool.manager_fee_account, + &referrer_token_account, &stake_pool.pool_mint, &spl_token::id(), ) @@ -681,6 +703,136 @@ fn command_deposit( Ok(()) } +fn command_deposit_sol( + config: &Config, + stake_pool_address: &Pubkey, + from: &Option, + pool_token_receiver_account: &Option, + referrer_token_account: &Option, + amount: f64, +) -> CommandResult { + if !config.no_update { + command_update(config, stake_pool_address, false, false)?; + } + + let amount = native_token::sol_to_lamports(amount); + + // Check withdraw_from balance + let from_pubkey = from.as_ref().map_or_else( + || config.fee_payer.try_pubkey().unwrap(), + |keypair| keypair.try_pubkey().unwrap(), + ); + let from_balance = config.rpc_client.get_balance(&from_pubkey)?; + if from_balance < amount { + return Err(format!( + "Not enough SOL to deposit into pool: {}.\nMaximum deposit amount is {} SOL.", + Sol(amount), + Sol(from_balance) + ) + .into()); + } + + let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; + + let mut instructions: Vec = vec![]; + + // ephemeral SOL account just to do the transfer + let user_sol_transfer = Keypair::new(); + let mut signers = vec![ + config.fee_payer.as_ref(), + config.staker.as_ref(), + &user_sol_transfer, + ]; + if let Some(keypair) = from.as_ref() { + signers.push(keypair) + } + + let mut total_rent_free_balances: u64 = 0; + + // Create the ephemeral SOL account + instructions.push(system_instruction::transfer( + &from_pubkey, + &user_sol_transfer.pubkey(), + amount, + )); + + // Create token account if not specified + let pool_token_receiver_account = + pool_token_receiver_account.unwrap_or(add_associated_token_account( + config, + &stake_pool.pool_mint, + &config.token_owner.pubkey(), + &mut instructions, + &mut total_rent_free_balances, + )); + + let referrer_token_account = referrer_token_account.unwrap_or(pool_token_receiver_account); + + let pool_withdraw_authority = + find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; + + let mut deposit_instructions = if let Some(sol_deposit_authority) = + config.sol_depositor.as_ref() + { + let expected_sol_deposit_authority = stake_pool.sol_deposit_authority.ok_or_else(|| { + "SOL deposit authority specified in arguments but stake pool has none".to_string() + })?; + signers.push(sol_deposit_authority.as_ref()); + if sol_deposit_authority.pubkey() != expected_sol_deposit_authority { + let error = format!( + "Invalid deposit authority specified, expected {}, received {}", + expected_sol_deposit_authority, + sol_deposit_authority.pubkey() + ); + return Err(error.into()); + } + + spl_stake_pool::instruction::deposit_sol_with_authority( + &spl_stake_pool::id(), + stake_pool_address, + &sol_deposit_authority.pubkey(), + &pool_withdraw_authority, + &stake_pool.reserve_stake, + &user_sol_transfer.pubkey(), + &pool_token_receiver_account, + &stake_pool.manager_fee_account, + &referrer_token_account, + &stake_pool.pool_mint, + &spl_token::id(), + amount, + ) + } else { + spl_stake_pool::instruction::deposit_sol( + &spl_stake_pool::id(), + stake_pool_address, + &pool_withdraw_authority, + &stake_pool.reserve_stake, + &user_sol_transfer.pubkey(), + &pool_token_receiver_account, + &stake_pool.manager_fee_account, + &referrer_token_account, + &stake_pool.pool_mint, + &spl_token::id(), + amount, + ) + }; + + instructions.append(&mut deposit_instructions); + + 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, + total_rent_free_balances + fee_calculator.calculate_fee(transaction.message()), + )?; + unique_signers!(signers); + transaction.sign(&signers, recent_blockhash); + send_transaction(config, transaction)?; + Ok(()) +} + fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult { let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; let validator_list = get_validator_list(&config.rpc_client, &stake_pool.validator_list)?; @@ -688,6 +840,9 @@ fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult { let epoch_info = config.rpc_client.get_epoch_info()?; let pool_withdraw_authority = find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; + let sol_deposit_authority = stake_pool + .sol_deposit_authority + .map_or("None".into(), |pubkey| pubkey.to_string()); if config.verbose { println!("Stake Pool Info"); @@ -696,7 +851,8 @@ fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult { println!("Validator List: {}", stake_pool.validator_list); println!("Manager: {}", stake_pool.manager); println!("Staker: {}", stake_pool.staker); - println!("Depositor: {}", stake_pool.deposit_authority); + println!("Depositor: {}", stake_pool.stake_deposit_authority); + println!("SOL Deposit Authority: {}", sol_deposit_authority); println!("Withdraw Authority: {}", pool_withdraw_authority); println!("Pool Token Mint: {}", stake_pool.pool_mint); println!("Fee Account: {}", stake_pool.manager_fee_account); @@ -718,13 +874,52 @@ fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult { preferred_withdraw_validator ); } - if stake_pool.fee.numerator > 0 { + + // Display fees information + if stake_pool.fee.numerator > 0 && stake_pool.fee.denominator > 0 { + println!("Epoch Fee: {} of epoch rewards", stake_pool.fee); + } else { + println!("Epoch Fee: none"); + } + if stake_pool.withdrawal_fee.numerator > 0 && stake_pool.withdrawal_fee.denominator > 0 { println!( - "Fee: {}/{} of epoch rewards", - stake_pool.fee.numerator, stake_pool.fee.denominator + "Withdrawal Fee: {} of withdrawal amount", + stake_pool.withdrawal_fee ); } else { - println!("Fee: none"); + println!("Withdrawal Fee: none"); + } + if stake_pool.stake_deposit_fee.numerator > 0 && stake_pool.stake_deposit_fee.denominator > 0 { + println!( + "Stake Deposit Fee: {} of staked amount", + stake_pool.stake_deposit_fee + ); + } else { + println!("Stake Deposit Fee: none"); + } + if stake_pool.sol_deposit_fee.numerator > 0 && stake_pool.sol_deposit_fee.denominator > 0 { + println!( + "SOL Deposit Fee: {} of deposit amount", + stake_pool.sol_deposit_fee + ); + } else { + println!("SOL Deposit Fee: none"); + } + if stake_pool.sol_referral_fee > 0 { + println!( + "SOL Deposit Referral Fee: {}% of SOL Deposit Fee", + stake_pool.sol_referral_fee + ); + } else { + println!("SOL Deposit Referral Fee: none"); + } + if stake_pool.stake_referral_fee > 0 { + println!( + "Stake Deposit Referral Fee: {}% of Stake Deposit Fee", + stake_pool.stake_referral_fee + ); + } else { + println!("Stake Deposit Referral Fee: none"); } if config.verbose { @@ -1079,7 +1274,7 @@ fn command_withdraw( stake_receiver_pubkey }); - instructions.push(spl_stake_pool::instruction::withdraw( + instructions.push(spl_stake_pool::instruction::withdraw_stake( &spl_stake_pool::id(), stake_pool_address, &stake_pool.validator_list, @@ -1179,16 +1374,22 @@ fn command_set_staker( Ok(()) } -fn command_set_fee(config: &Config, stake_pool_address: &Pubkey, new_fee: Fee) -> CommandResult { +fn command_set_deposit_authority( + config: &Config, + stake_pool_address: &Pubkey, + new_sol_deposit_authority: Option, + for_stake_deposit: bool, +) -> CommandResult { let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()]; unique_signers!(signers); let transaction = checked_transaction_with_signers( config, - &[spl_stake_pool::instruction::set_fee( + &[spl_stake_pool::instruction::set_deposit_authority( &spl_stake_pool::id(), stake_pool_address, &config.manager.pubkey(), - new_fee, + new_sol_deposit_authority.as_ref(), + for_stake_deposit, )], &signers, )?; @@ -1196,16 +1397,16 @@ fn command_set_fee(config: &Config, stake_pool_address: &Pubkey, new_fee: Fee) - Ok(()) } -fn command_set_withdrawal_fee( +fn command_set_fee( config: &Config, stake_pool_address: &Pubkey, - new_fee: Fee, + new_fee: FeeType, ) -> CommandResult { let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()]; unique_signers!(signers); let transaction = checked_transaction_with_signers( config, - &[spl_stake_pool::instruction::set_withdrawal_fee( + &[spl_stake_pool::instruction::set_fee( &spl_stake_pool::id(), stake_pool_address, &config.manager.pubkey(), @@ -1366,6 +1567,31 @@ fn main() { .requires("withdrawal_fee_numerator") .help("Withdrawal fee denominator, fee amount is numerator divided by denominator [default: 0]"), ) + .arg( + Arg::with_name("deposit_fee_numerator") + .long("deposit-fee-numerator") + .validator(is_parsable::) + .value_name("NUMERATOR") + .takes_value(true) + .requires("deposit_fee_denominator") + .help("Deposit fee numerator, fee amount is numerator divided by denominator [default: 0]"), + ).arg( + Arg::with_name("deposit_fee_denominator") + .long("deposit-fee-denominator") + .validator(is_parsable::) + .value_name("DENOMINATOR") + .takes_value(true) + .requires("deposit_fee_numerator") + .help("Deposit fee denominator, fee amount is numerator divided by denominator [default: 0]"), + ) + .arg( + Arg::with_name("referral_fee") + .long("referral-fee") + .validator(is_valid_percentage) + .value_name("FEE_PERCENTAGE") + .takes_value(true) + .help("Referral fee percentage, maximum 100"), + ) .arg( Arg::with_name("max_validators") .long("max-validators") @@ -1377,11 +1603,11 @@ fn main() { .help("Max number of validators included in the stake pool"), ) .arg( - Arg::with_name("deposit_authority") - .long("deposit-authority") + Arg::with_name("stake_deposit_authority") + .long("stake-deposit-authority") .short("a") - .validator(is_pubkey) - .value_name("DEPOSIT_AUTHORITY_ADDRESS") + .validator(is_keypair_or_ask_keyword) + .value_name("STAKE_DEPOSIT_AUTHORITY_KEYPAIR") .takes_value(true) .help("Deposit authority required to sign all deposits into the stake pool"), ) @@ -1581,8 +1807,8 @@ fn main() { .required(true) ) ) - .subcommand(SubCommand::with_name("deposit") - .about("Add stake account to the stake pool") + .subcommand(SubCommand::with_name("deposit-stake") + .about("Deposit active stake account into the stake pool in exchange for pool tokens") .arg( Arg::with_name("pool") .index(1) @@ -1611,6 +1837,61 @@ fn main() { Defaults to the token-owner's associated pool token account. \ Creates the account if it does not exist."), ) + .arg( + Arg::with_name("referrer") + .validator(is_pubkey) + .value_name("ADDRESS") + .takes_value(true) + .help("Pool token account to receive the referral fees for deposits. \ + Defaults to the token receiver."), + ) + ) + .subcommand(SubCommand::with_name("deposit-sol") + .about("Deposit SOL into the stake pool in exchange for pool tokens") + .arg( + Arg::with_name("pool") + .index(1) + .validator(is_pubkey) + .value_name("POOL_ADDRESS") + .takes_value(true) + .required(true) + .help("Stake pool address"), + ).arg( + Arg::with_name("amount") + .index(2) + .validator(is_amount) + .value_name("AMOUNT") + .takes_value(true) + .help("Amount in SOL to deposit into the stake pool reserve account."), + ) + .arg( + Arg::with_name("from") + .long("from") + .validator(is_keypair_or_ask_keyword) + .value_name("KEYPAIR") + .takes_value(true) + .help("Source account of funds. \ + Defaults to the fee payer."), + ) + .arg( + Arg::with_name("token_receiver") + .long("token-receiver") + .validator(is_pubkey) + .value_name("POOL_TOKEN_RECEIVER_ADDRESS") + .takes_value(true) + .help("Account to receive the minted pool tokens. \ + Defaults to the token-owner's associated pool token account. \ + Creates the account if it does not exist."), + ) + .arg( + Arg::with_name("referrer") + .long("referrer") + .validator(is_pubkey) + .value_name("REFERRER_TOKEN_ADDRESS") + .takes_value(true) + .help("Account to receive the referral fees for deposits. \ + Defaults to the token receiver."), + ) ) .subcommand(SubCommand::with_name("list") .about("List stake accounts managed by this pool") @@ -1648,7 +1929,7 @@ fn main() { .help("Do not automatically merge transient stakes. Useful if the stake pool is in an expected state, but the balances still need to be updated."), ) ) - .subcommand(SubCommand::with_name("withdraw") + .subcommand(SubCommand::with_name("withdraw-stake") .about("Withdraw amount from the stake pool") .arg( Arg::with_name("pool") @@ -1758,8 +2039,8 @@ fn main() { .help("Public key for the new stake pool staker."), ) ) - .subcommand(SubCommand::with_name("set-fee") - .about("Change the fee assessed by the stake pool. Must be signed by the manager.") + .subcommand(SubCommand::with_name("set-sol-deposit-authority") + .about("Change sol deposit authority account for the stake pool. Must be signed by the manager.") .arg( Arg::with_name("pool") .index(1) @@ -1770,8 +2051,78 @@ fn main() { .help("Stake pool address."), ) .arg( - Arg::with_name("fee_numerator") + Arg::with_name("new_sol_deposit_authority") .index(2) + .validator(is_pubkey) + .value_name("ADDRESS") + .takes_value(true) + .help("The public key for the new stake pool sol deposit authority."), + ) + .arg( + Arg::with_name("unset") + .long("unset") + .takes_value(false) + .help("Unset the sol deposit authority.") + ) + .group(ArgGroup::with_name("validator") + .arg("new_sol_deposit_authority") + .arg("unset") + .required(true) + ) + ) + .subcommand(SubCommand::with_name("set-stake-deposit-authority") + .about("Change stake deposit authority account for the stake pool. Must be signed by the manager.") + .arg( + Arg::with_name("pool") + .index(1) + .validator(is_pubkey) + .value_name("POOL_ADDRESS") + .takes_value(true) + .required(true) + .help("Stake pool address."), + ) + .arg( + Arg::with_name("new_stake_deposit_authority") + .index(2) + .validator(is_pubkey) + .value_name("ADDRESS_OR_NONE") + .takes_value(true) + .help("'none', or a public key for the new stake pool sol deposit authority."), + ) + .arg( + Arg::with_name("unset") + .long("unset") + .takes_value(false) + .help("Unset the stake deposit authority. The program will use a program derived address.") + ) + .group(ArgGroup::with_name("validator") + .arg("new_stake_deposit_authority") + .arg("unset") + .required(true) + ) + ) + .subcommand(SubCommand::with_name("set-fee") + .about("Change the [management/withdrawal/stake deposit/sol deposit] fee assessed by the stake pool. Must be signed by the manager.") + .arg( + Arg::with_name("pool") + .index(1) + .validator(is_pubkey) + .value_name("POOL_ADDRESS") + .takes_value(true) + .required(true) + .help("Stake pool address."), + ) + .arg(Arg::with_name("fee_type") + .index(2) + .value_name("FEE_TYPE") + .possible_values(&["epoch", "stake-deposit", "sol-deposit", "withdrawal"]) // PreferredValidatorType enum + .takes_value(true) + .required(true) + .help("Fee type to be updated."), + ) + .arg( + Arg::with_name("fee_numerator") + .index(3) .validator(is_parsable::) .value_name("NUMERATOR") .takes_value(true) @@ -1780,7 +2131,7 @@ fn main() { ) .arg( Arg::with_name("fee_denominator") - .index(3) + .index(4) .validator(is_parsable::) .value_name("DENOMINATOR") .takes_value(true) @@ -1788,8 +2139,8 @@ fn main() { .help("Fee denominator, fee amount is numerator divided by denominator."), ) ) - .subcommand(SubCommand::with_name("set-withdrawal-fee") - .about("Change the withdrawal fee assessed by the stake pool. Must be signed by the manager.") + .subcommand(SubCommand::with_name("set-stake-referral-fee") + .about("Change the referral fee assessed by the stake pool for stake deposits. Must be signed by the manager.") .arg( Arg::with_name("pool") .index(1) @@ -1800,22 +2151,33 @@ fn main() { .help("Stake pool address."), ) .arg( - Arg::with_name("fee_numerator") + Arg::with_name("fee") .index(2) - .validator(is_parsable::) - .value_name("NUMERATOR") + .validator(is_valid_percentage) + .value_name("FEE_PERCENTAGE") .takes_value(true) .required(true) - .help("Fee numerator, fee amount is numerator divided by denominator."), + .help("Fee percentage, maximum 100"), + ) + ).subcommand(SubCommand::with_name("set-sol-referral-fee") + .about("Change the referral fee assessed by the stake pool for SOL deposits. Must be signed by the manager.") + .arg( + Arg::with_name("pool") + .index(1) + .validator(is_pubkey) + .value_name("POOL_ADDRESS") + .takes_value(true) + .required(true) + .help("Stake pool address."), ) .arg( - Arg::with_name("fee_denominator") - .index(3) - .validator(is_parsable::) - .value_name("DENOMINATOR") + Arg::with_name("fee") + .index(2) + .validator(is_valid_percentage) + .value_name("FEE_PERCENTAGE") .takes_value(true) .required(true) - .help("Fee denominator, fee amount is numerator divided by denominator."), + .help("Fee percentage, maximum 100"), ) ) .get_matches(); @@ -1847,6 +2209,16 @@ fn main() { } else { None }; + let sol_depositor = if matches.is_present("sol_depositor") { + Some(get_signer( + &matches, + "sol_depositor", + &cli_config.keypair_path, + &mut wallet_manager, + )) + } else { + None + }; let manager = get_signer( &matches, "manager", @@ -1875,6 +2247,7 @@ fn main() { manager, staker, depositor, + sol_depositor, token_owner, fee_payer, dry_run, @@ -1884,18 +2257,21 @@ fn main() { let _ = match matches.subcommand() { ("create-pool", Some(arg_matches)) => { - let deposit_authority = pubkey_of(arg_matches, "deposit_authority"); + let stake_deposit_authority = keypair_of(arg_matches, "stake_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 w_numerator = value_t!(arg_matches, "withdrawal_fee_numerator", u64); let w_denominator = value_t!(arg_matches, "withdrawal_fee_denominator", u64); + let d_numerator = value_t!(arg_matches, "deposit_fee_numerator", u64); + let d_denominator = value_t!(arg_matches, "deposit_fee_denominator", u64); + let referral_fee = value_t!(arg_matches, "referral_fee", u8); let max_validators = value_t_or_exit!(arg_matches, "max_validators", u32); let pool_keypair = keypair_of(arg_matches, "pool_keypair"); let mint_keypair = keypair_of(arg_matches, "mint_keypair"); let reserve_keypair = keypair_of(arg_matches, "reserve_keypair"); command_create_pool( &config, - deposit_authority, + stake_deposit_authority, Fee { denominator, numerator, @@ -1904,6 +2280,11 @@ fn main() { numerator: w_numerator.unwrap_or(0), denominator: w_denominator.unwrap_or(0), }, + Fee { + numerator: d_numerator.unwrap_or(0), + denominator: d_denominator.unwrap_or(0), + }, + referral_fee.unwrap_or(0), max_validators, pool_keypair, mint_keypair, @@ -1956,15 +2337,32 @@ fn main() { vote_account, ) } - ("deposit", Some(arg_matches)) => { + ("deposit-stake", Some(arg_matches)) => { let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); let stake_account = pubkey_of(arg_matches, "stake_account").unwrap(); let token_receiver: Option = pubkey_of(arg_matches, "token_receiver"); - command_deposit( + let referrer: Option = pubkey_of(arg_matches, "referrer"); + command_deposit_stake( &config, &stake_pool_address, &stake_account, &token_receiver, + &referrer, + ) + } + ("deposit-sol", Some(arg_matches)) => { + let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); + let token_receiver: Option = pubkey_of(arg_matches, "token_receiver"); + let referrer: Option = pubkey_of(arg_matches, "referrer"); + let from = keypair_of(arg_matches, "from"); + let amount = value_t_or_exit!(arg_matches, "amount", f64); + command_deposit_sol( + &config, + &stake_pool_address, + &from, + &token_receiver, + &referrer, + amount, ) } ("list", Some(arg_matches)) => { @@ -1977,7 +2375,7 @@ fn main() { let force = arg_matches.is_present("force"); command_update(&config, &stake_pool_address, force, no_merge) } - ("withdraw", Some(arg_matches)) => { + ("withdraw-stake", Some(arg_matches)) => { let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); let vote_account = pubkey_of(arg_matches, "vote_account"); let pool_account = pubkey_of(arg_matches, "pool_account"); @@ -2010,6 +2408,28 @@ fn main() { let new_staker = pubkey_of(arg_matches, "new_staker").unwrap(); command_set_staker(&config, &stake_pool_address, &new_staker) } + ("set-stake-deposit-authority", Some(arg_matches)) => { + let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); + let new_stake_deposit_authority = pubkey_of(arg_matches, "new_stake_deposit_authority"); + let _unset = arg_matches.is_present("unset"); + command_set_deposit_authority( + &config, + &stake_pool_address, + new_stake_deposit_authority, + true, + ) + } + ("set-sol-deposit-authority", Some(arg_matches)) => { + let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); + let new_sol_deposit_authority = pubkey_of(arg_matches, "new_sol_deposit_authority"); + let _unset = arg_matches.is_present("unset"); + command_set_deposit_authority( + &config, + &stake_pool_address, + new_sol_deposit_authority, + false, + ) + } ("set-fee", Some(arg_matches)) => { let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); let numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64); @@ -2018,17 +2438,35 @@ fn main() { denominator, numerator, }; - command_set_fee(&config, &stake_pool_address, new_fee) + match arg_matches.value_of("fee_type").unwrap() { + "epoch" => command_set_fee(&config, &stake_pool_address, FeeType::Epoch(new_fee)), + "stake-deposit" => { + command_set_fee(&config, &stake_pool_address, FeeType::StakeDeposit(new_fee)) + } + "sol-deposit" => { + command_set_fee(&config, &stake_pool_address, FeeType::SolDeposit(new_fee)) + } + "withdrawal" => { + command_set_fee(&config, &stake_pool_address, FeeType::Withdrawal(new_fee)) + } + _ => unreachable!(), + } } - ("set-withdrawal-fee", Some(arg_matches)) => { + ("set-stake-referral-fee", Some(arg_matches)) => { let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); - let numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64); - let denominator = value_t_or_exit!(arg_matches, "fee_denominator", u64); - let new_fee = Fee { - denominator, - numerator, - }; - command_set_withdrawal_fee(&config, &stake_pool_address, new_fee) + let fee = value_t_or_exit!(arg_matches, "fee", u8); + if fee > 100u8 { + panic!("Invalid fee {}%. Fee needs to be in range [0-100]", fee); + } + command_set_fee(&config, &stake_pool_address, FeeType::StakeReferral(fee)) + } + ("set-sol-referral-fee", Some(arg_matches)) => { + let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); + let fee = value_t_or_exit!(arg_matches, "fee", u8); + if fee > 100u8 { + panic!("Invalid fee {}%. Fee needs to be in range [0-100]", fee); + } + command_set_fee(&config, &stake_pool_address, FeeType::SolReferral(fee)) } _ => unreachable!(), } diff --git a/stake-pool/program/src/error.rs b/stake-pool/program/src/error.rs index 55e45b7e..c526b4b1 100644 --- a/stake-pool/program/src/error.rs +++ b/stake-pool/program/src/error.rs @@ -108,6 +108,14 @@ pub enum StakePoolError { /// Not enough lamports provided for deposit to result in one pool token #[error("DepositTooSmall")] DepositTooSmall, + + // 30. + /// Provided stake deposit authority does not match the program's + #[error("FeeIncreaseTooHigh")] + InvalidStakeDepositAuthority, + /// Provided sol deposit authority does not match the program's + #[error("InvalidSolDepositAuthority")] + InvalidSolDepositAuthority, } impl From for ProgramError { fn from(e: StakePoolError) -> Self { diff --git a/stake-pool/program/src/instruction.rs b/stake-pool/program/src/instruction.rs index ab35ef66..0eecffa2 100644 --- a/stake-pool/program/src/instruction.rs +++ b/stake-pool/program/src/instruction.rs @@ -6,7 +6,7 @@ use { find_deposit_authority_program_address, find_stake_program_address, find_transient_stake_program_address, find_withdraw_authority_program_address, stake_program, - state::{Fee, StakePool, ValidatorList}, + state::{Fee, FeeType, StakePool, ValidatorList}, MAX_VALIDATORS_TO_UPDATE, }, borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, @@ -28,6 +28,17 @@ pub enum PreferredValidatorType { Withdraw, } +/// Defines which validator vote account is set during the +/// `SetPreferredValidator` instruction +#[repr(C)] +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +pub enum DepositType { + /// Set preferred validator for deposits + Stake, + /// Set preferred validator for withdraws + Sol, +} + /// Instructions supported by the StakePool program. #[repr(C)] #[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] @@ -55,6 +66,12 @@ pub enum StakePoolInstruction { /// Fee charged per withdrawal as percentage of withdrawal #[allow(dead_code)] // but it's not withdrawal_fee: Fee, + /// Fee charged per deposit as percentage of deposit + #[allow(dead_code)] // but it's not + deposit_fee: Fee, + /// Percentage [0-100] of deposit_fee that goes to referrer + #[allow(dead_code)] // but it's not + referral_fee: u8, /// Maximum expected number of validators #[allow(dead_code)] // but it's not max_validators: u32, @@ -244,18 +261,20 @@ pub enum StakePoolInstruction { /// /// 0. `[w]` Stake pool /// 1. `[w]` Validator stake list storage account - /// 2. `[]` Stake pool deposit authority + /// 2. `[s]/[]` Stake pool deposit authority /// 3. `[]` Stake pool withdraw authority /// 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]` Reserve stake account, to withdraw rent exempt reserve /// 7. `[w]` User account to receive pool tokens - /// 8. `[w]` Pool token mint account - /// 9. '[]' Sysvar clock account - /// 10. '[]' Sysvar stake history account - /// 11. `[]` Pool token program id, - /// 12. `[]` Stake program id, - Deposit, + /// 8. `[w]` Account to receive pool fee tokens + /// 9. `[w]` Account to receive a portion of pool fee tokens as referral fees + /// 10. `[w]` Pool token mint account + /// 11. '[]' Sysvar clock account + /// 12. '[]' Sysvar stake history account + /// 13. `[]` Pool token program id, + /// 14. `[]` Stake program id, + DepositStake, /// Withdraw the token from the pool at the current ratio. /// @@ -281,7 +300,7 @@ pub enum StakePoolInstruction { /// 11. `[]` Pool token program id /// 12. `[]` Stake program id, /// userdata: amount of pool tokens to withdraw - Withdraw(u64), + WithdrawStake(u64), /// (Manager only) Update manager /// @@ -297,9 +316,9 @@ pub enum StakePoolInstruction { /// 1. `[s]` Manager /// 2. `[]` Sysvar clock SetFee { - /// Fee assessed as percentage of perceived rewards + /// Type of fee to update and value to update it to #[allow(dead_code)] // but it's not - fee: Fee, + fee: FeeType, }, /// (Manager or staker only) Update staker @@ -309,16 +328,29 @@ pub enum StakePoolInstruction { /// 2. '[]` New staker pubkey SetStaker, - /// (Manager only) Update Withdrawal fee for next epoch + /// Deposit SOL directly into the pool's reserve account. The output is a "pool" token + /// representing ownership into the pool. Inputs are converted to the current ratio. + /// + /// 0. `[w]` Stake pool + /// 1. `[s]/[]` Stake pool sol deposit authority. + /// 2. `[]` Stake pool withdraw authority + /// 3. `[w]` Reserve stake account, to withdraw rent exempt reserve + /// 4. `[s]` Account providing the lamports to be deposited into the pool + /// 5. `[w]` User account to receive pool tokens + /// 6. `[w]` Account to receive pool fee tokens + /// 7. `[w]` Account to receive a portion of pool fee tokens as referral fees + /// 8. `[w]` Pool token mint account + /// 9. '[]' Sysvar clock account + /// 10 `[]` System program account + /// 11. `[]` Pool token program id, + DepositSol(u64), + + /// (Manager only) Update SOL deposit authority /// /// 0. `[w]` StakePool /// 1. `[s]` Manager - /// 2. `[]` Sysvar clock - SetWithdrawalFee { - /// Fee assessed as percentage of perceived rewards - #[allow(dead_code)] // but it's not - fee: Fee, - }, + /// 2. '[]` New sol_deposit_authority pubkey or none + SetDepositAuthority(DepositType), } /// Creates an 'initialize' instruction. @@ -335,11 +367,15 @@ pub fn initialize( deposit_authority: Option, fee: Fee, withdrawal_fee: Fee, + deposit_fee: Fee, + referral_fee: u8, max_validators: u32, ) -> Instruction { let init_data = StakePoolInstruction::Initialize { fee, withdrawal_fee, + deposit_fee, + referral_fee, max_validators, }; let data = init_data.try_to_vec().unwrap(); @@ -826,7 +862,7 @@ pub fn update_stake_pool( /// Creates instructions required to deposit into a stake pool, given a stake /// account owned by the user. -pub fn deposit( +pub fn deposit_stake( program_id: &Pubkey, stake_pool: &Pubkey, validator_list_storage: &Pubkey, @@ -836,6 +872,8 @@ pub fn deposit( validator_stake_account: &Pubkey, reserve_stake_account: &Pubkey, pool_tokens_to: &Pubkey, + manager_fee_account: &Pubkey, + referrer_pool_tokens_account: &Pubkey, pool_mint: &Pubkey, token_program_id: &Pubkey, ) -> Vec { @@ -850,6 +888,8 @@ pub fn deposit( AccountMeta::new(*validator_stake_account, false), AccountMeta::new(*reserve_stake_account, false), AccountMeta::new(*pool_tokens_to, false), + AccountMeta::new(*manager_fee_account, false), + AccountMeta::new(*referrer_pool_tokens_account, false), AccountMeta::new(*pool_mint, false), AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false), @@ -872,7 +912,7 @@ pub fn deposit( Instruction { program_id: *program_id, accounts, - data: StakePoolInstruction::Deposit.try_to_vec().unwrap(), + data: StakePoolInstruction::DepositStake.try_to_vec().unwrap(), }, ] } @@ -880,7 +920,7 @@ pub fn deposit( /// 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( +pub fn deposit_stake_with_authority( program_id: &Pubkey, stake_pool: &Pubkey, validator_list_storage: &Pubkey, @@ -891,6 +931,8 @@ pub fn deposit_with_authority( validator_stake_account: &Pubkey, reserve_stake_account: &Pubkey, pool_tokens_to: &Pubkey, + manager_fee_account: &Pubkey, + referrer_pool_tokens_account: &Pubkey, pool_mint: &Pubkey, token_program_id: &Pubkey, ) -> Vec { @@ -903,6 +945,8 @@ pub fn deposit_with_authority( AccountMeta::new(*validator_stake_account, false), AccountMeta::new(*reserve_stake_account, false), AccountMeta::new(*pool_tokens_to, false), + AccountMeta::new(*manager_fee_account, false), + AccountMeta::new(*referrer_pool_tokens_account, false), AccountMeta::new(*pool_mint, false), AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false), @@ -925,13 +969,91 @@ pub fn deposit_with_authority( Instruction { program_id: *program_id, accounts, - data: StakePoolInstruction::Deposit.try_to_vec().unwrap(), + data: StakePoolInstruction::DepositStake.try_to_vec().unwrap(), }, ] } -/// Creates a 'withdraw' instruction. -pub fn withdraw( +/// Creates instructions required to deposit SOL directly into a stake pool. +pub fn deposit_sol( + program_id: &Pubkey, + stake_pool: &Pubkey, + stake_pool_withdraw_authority: &Pubkey, + reserve_stake_account: &Pubkey, + lamports_from: &Pubkey, + pool_tokens_to: &Pubkey, + manager_fee_account: &Pubkey, + referrer_pool_tokens_account: &Pubkey, + pool_mint: &Pubkey, + token_program_id: &Pubkey, + amount: u64, +) -> Vec { + let accounts = vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new_readonly(*stake_pool_withdraw_authority, false), + AccountMeta::new(*reserve_stake_account, false), + AccountMeta::new(*lamports_from, true), + AccountMeta::new(*pool_tokens_to, false), + AccountMeta::new(*manager_fee_account, false), + AccountMeta::new(*referrer_pool_tokens_account, false), + AccountMeta::new(*pool_mint, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(*token_program_id, false), + ]; + vec![Instruction { + program_id: *program_id, + accounts, + data: StakePoolInstruction::DepositSol(amount) + .try_to_vec() + .unwrap(), + }] +} + +/// Creates instructions required to deposit SOL directly into a stake pool. +/// The difference with `deposit_sol()` is that a deposit +/// authority must sign this instruction, which is required for private pools. +/// `require_deposit_authority` should be false only if +/// `sol_deposit_authority == None` +pub fn deposit_sol_with_authority( + program_id: &Pubkey, + stake_pool: &Pubkey, + sol_deposit_authority: &Pubkey, + stake_pool_withdraw_authority: &Pubkey, + reserve_stake_account: &Pubkey, + lamports_from: &Pubkey, + pool_tokens_to: &Pubkey, + manager_fee_account: &Pubkey, + referrer_pool_tokens_account: &Pubkey, + pool_mint: &Pubkey, + token_program_id: &Pubkey, + amount: u64, +) -> Vec { + let accounts = vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new_readonly(*stake_pool_withdraw_authority, false), + AccountMeta::new(*reserve_stake_account, false), + AccountMeta::new(*lamports_from, true), + AccountMeta::new(*pool_tokens_to, false), + AccountMeta::new(*manager_fee_account, false), + AccountMeta::new(*referrer_pool_tokens_account, false), + AccountMeta::new(*pool_mint, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(*token_program_id, false), + AccountMeta::new_readonly(*sol_deposit_authority, true), + ]; + vec![Instruction { + program_id: *program_id, + accounts, + data: StakePoolInstruction::DepositSol(amount) + .try_to_vec() + .unwrap(), + }] +} + +/// Creates a 'WithdrawStake' instruction. +pub fn withdraw_stake( program_id: &Pubkey, stake_pool: &Pubkey, validator_list_storage: &Pubkey, @@ -964,7 +1086,9 @@ pub fn withdraw( Instruction { program_id: *program_id, accounts, - data: StakePoolInstruction::Withdraw(amount).try_to_vec().unwrap(), + data: StakePoolInstruction::WithdrawStake(amount) + .try_to_vec() + .unwrap(), } } @@ -994,7 +1118,7 @@ pub fn set_fee( program_id: &Pubkey, stake_pool: &Pubkey, manager: &Pubkey, - fee: Fee, + fee: FeeType, ) -> Instruction { let accounts = vec![ AccountMeta::new(*stake_pool, false), @@ -1008,25 +1132,64 @@ pub fn set_fee( } } -/// Creates a 'set fee' instruction. +/// Creates a 'set fee' instruction for setting the epoch fee +pub fn set_epoch_fee( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, + fee: Fee, +) -> Instruction { + set_fee(program_id, stake_pool, manager, FeeType::Epoch(fee)) +} + +/// Creates a 'set fee' instruction for setting withdrawal fee pub fn set_withdrawal_fee( program_id: &Pubkey, stake_pool: &Pubkey, manager: &Pubkey, fee: Fee, ) -> Instruction { - let accounts = vec![ - AccountMeta::new(*stake_pool, false), - AccountMeta::new_readonly(*manager, true), - AccountMeta::new_readonly(sysvar::clock::id(), false), - ]; - Instruction { - program_id: *program_id, - accounts, - data: StakePoolInstruction::SetWithdrawalFee { fee } - .try_to_vec() - .unwrap(), - } + set_fee(program_id, stake_pool, manager, FeeType::Withdrawal(fee)) +} + +/// Creates a 'set fee' instruction for setting SOL deposit fee +pub fn set_sol_deposit_fee( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, + fee: Fee, +) -> Instruction { + set_fee(program_id, stake_pool, manager, FeeType::SolDeposit(fee)) +} + +/// Creates a 'set fee' instruction for setting stake deposit fee +pub fn set_stake_deposit_fee( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, + fee: Fee, +) -> Instruction { + set_fee(program_id, stake_pool, manager, FeeType::StakeDeposit(fee)) +} + +/// Creates a 'set fee' instruction for setting SOL referral fee +pub fn set_sol_referral_fee( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, + fee: u8, +) -> Instruction { + set_fee(program_id, stake_pool, manager, FeeType::SolReferral(fee)) +} + +/// Creates a 'set fee' instruction for setting stake referral fee +pub fn set_stake_referral_fee( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, + fee: u8, +) -> Instruction { + set_fee(program_id, stake_pool, manager, FeeType::StakeReferral(fee)) } /// Creates a 'set staker' instruction. @@ -1047,3 +1210,79 @@ pub fn set_staker( data: StakePoolInstruction::SetStaker.try_to_vec().unwrap(), } } + +/// Creates a 'set sol deposit authority' instruction. +pub fn set_deposit_authority( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, + new_sol_deposit_authority: Option<&Pubkey>, + for_stake_deposit: bool, +) -> Instruction { + let mut accounts = vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new_readonly(*manager, true), + ]; + if let Some(auth) = new_sol_deposit_authority { + accounts.push(AccountMeta::new_readonly(*auth, false)) + } + Instruction { + program_id: *program_id, + accounts, + data: if for_stake_deposit { + StakePoolInstruction::SetDepositAuthority(DepositType::Stake) + .try_to_vec() + .unwrap() + } else { + StakePoolInstruction::SetDepositAuthority(DepositType::Sol) + .try_to_vec() + .unwrap() + }, + } +} + +/// Creates a 'set stake deposit authority' instruction. +pub fn set_stake_deposit_authority( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, + new_stake_deposit_authority: Option<&Pubkey>, +) -> Instruction { + let mut accounts = vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new_readonly(*manager, true), + ]; + if let Some(auth) = new_stake_deposit_authority { + accounts.push(AccountMeta::new_readonly(*auth, false)) + } + Instruction { + program_id: *program_id, + accounts, + data: StakePoolInstruction::SetDepositAuthority(DepositType::Stake) + .try_to_vec() + .unwrap(), + } +} + +/// Creates a 'set stake deposit authority' instruction. +pub fn set_sol_deposit_authority( + program_id: &Pubkey, + stake_pool: &Pubkey, + manager: &Pubkey, + new_stake_deposit_authority: Option<&Pubkey>, +) -> Instruction { + let mut accounts = vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new_readonly(*manager, true), + ]; + if let Some(auth) = new_stake_deposit_authority { + accounts.push(AccountMeta::new_readonly(*auth, false)) + } + Instruction { + program_id: *program_id, + accounts, + data: StakePoolInstruction::SetDepositAuthority(DepositType::Sol) + .try_to_vec() + .unwrap(), + } +} diff --git a/stake-pool/program/src/lib.rs b/stake-pool/program/src/lib.rs index 4ba5cf4d..0c71bb41 100644 --- a/stake-pool/program/src/lib.rs +++ b/stake-pool/program/src/lib.rs @@ -51,6 +51,7 @@ pub const WITHDRAWAL_BASELINE_FEE: Fee = Fee { /// Get the stake amount under consideration when calculating pool token /// conversions +#[inline] pub fn minimum_stake_lamports(meta: &Meta) -> u64 { meta.rent_exempt_reserve .saturating_add(MINIMUM_ACTIVE_STAKE) @@ -58,6 +59,7 @@ pub fn minimum_stake_lamports(meta: &Meta) -> u64 { /// Get the stake amount under consideration when calculating pool token /// conversions +#[inline] pub fn minimum_reserve_lamports(meta: &Meta) -> u64 { meta.rent_exempt_reserve.saturating_add(1) } diff --git a/stake-pool/program/src/processor.rs b/stake-pool/program/src/processor.rs index 4a3e10a8..15cd4719 100644 --- a/stake-pool/program/src/processor.rs +++ b/stake-pool/program/src/processor.rs @@ -1,5 +1,6 @@ //! Program state processor +use crate::instruction::DepositType; use { crate::{ error::StakePoolError, @@ -7,11 +8,10 @@ use { instruction::{PreferredValidatorType, StakePoolInstruction}, minimum_reserve_lamports, minimum_stake_lamports, stake_program, state::{ - AccountType, Fee, StakePool, StakeStatus, ValidatorList, ValidatorListHeader, + AccountType, Fee, FeeType, StakePool, StakeStatus, ValidatorList, ValidatorListHeader, ValidatorStakeInfo, }, - AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MAX_WITHDRAWAL_FEE_INCREASE, MINIMUM_ACTIVE_STAKE, - TRANSIENT_STAKE_SEED, WITHDRAWAL_BASELINE_FEE, + AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MINIMUM_ACTIVE_STAKE, TRANSIENT_STAKE_SEED, }, borsh::{BorshDeserialize, BorshSerialize}, num_traits::FromPrimitive, @@ -468,12 +468,24 @@ impl Processor { invoke(&ix, &[source, destination, authority, token_program]) } + fn sol_transfer<'a>( + source: AccountInfo<'a>, + destination: AccountInfo<'a>, + system_program: AccountInfo<'a>, + amount: u64, + ) -> Result<(), ProgramError> { + let ix = solana_program::system_instruction::transfer(source.key, destination.key, amount); + invoke(&ix, &[source, destination, system_program]) + } + /// Processes `Initialize` instruction. fn process_initialize( program_id: &Pubkey, accounts: &[AccountInfo], fee: Fee, withdrawal_fee: Fee, + stake_deposit_fee: Fee, + stake_referral_fee: u8, max_validators: u32, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); @@ -538,7 +550,11 @@ impl Processor { } // Numerator should be smaller than or equal to denominator (fee <= 1) - if fee.numerator > fee.denominator { + if fee.numerator > fee.denominator + || withdrawal_fee.numerator > withdrawal_fee.denominator + || stake_deposit_fee.numerator > stake_deposit_fee.denominator + || stake_referral_fee > 100u8 + { return Err(StakePoolError::FeeTooHigh.into()); } @@ -556,11 +572,11 @@ impl Processor { return Err(StakePoolError::WrongAccountMint.into()); } - let deposit_authority = match next_account_info(account_info_iter) { - Ok(deposit_authority_info) => *deposit_authority_info.key, + let stake_deposit_authority = match next_account_info(account_info_iter) { + Ok(stake_deposit_authority_info) => *stake_deposit_authority_info.key, Err(_) => find_deposit_authority_program_address(program_id, stake_pool_info.key).0, }; - let (withdraw_authority_key, withdraw_bump_seed) = + let (withdraw_authority_key, stake_withdraw_bump_seed) = crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key); let pool_mint = Mint::unpack_from_slice(&pool_mint_info.data.borrow())?; @@ -616,10 +632,6 @@ impl Processor { msg!("Reserve stake account not in intialized state"); return Err(StakePoolError::WrongStakeState.into()); }; - // Numerator should be smaller than or equal to denominator (fee <= 1) - if withdrawal_fee.numerator > withdrawal_fee.denominator { - return Err(StakePoolError::FeeTooHigh.into()); - } validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?; @@ -627,8 +639,8 @@ 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_authority = deposit_authority; - stake_pool.withdraw_bump_seed = withdraw_bump_seed; + stake_pool.stake_deposit_authority = stake_deposit_authority; + stake_pool.stake_withdraw_bump_seed = stake_withdraw_bump_seed; stake_pool.validator_list = *validator_list_info.key; stake_pool.pool_mint = *pool_mint_info.key; stake_pool.manager_fee_account = *manager_fee_info.key; @@ -639,9 +651,11 @@ impl Processor { stake_pool.next_epoch_fee = None; stake_pool.preferred_deposit_validator_vote_address = None; stake_pool.preferred_withdraw_validator_vote_address = None; - stake_pool.deposit_fee = Fee::default(); + stake_pool.stake_deposit_fee = stake_deposit_fee; stake_pool.withdrawal_fee = withdrawal_fee; stake_pool.next_withdrawal_fee = None; + stake_pool.stake_referral_fee = stake_referral_fee; + stake_pool.sol_deposit_authority = None; stake_pool .serialize(&mut *stake_pool_info.data.borrow_mut()) @@ -798,7 +812,7 @@ impl Processor { )?; if meta.lockup != stake_program::Lockup::default() { - msg!("Validator stake account has a lockup"); + msg!("Stake account has a lockup"); return Err(StakePoolError::WrongStakeState.into()); } @@ -813,20 +827,13 @@ impl Processor { // Check amount of lamports let stake_lamports = **stake_account_info.lamports.borrow(); let minimum_lamport_amount = minimum_stake_lamports(&meta); - if stake_lamports != minimum_lamport_amount { + if stake_lamports != minimum_lamport_amount + || stake.delegation.stake != MINIMUM_ACTIVE_STAKE + { msg!( - "Error: attempting to add stake with {} lamports, must have {} lamports", + "Error: attempting to add (stake: {}, delegation: {}), below minimum", stake_lamports, - minimum_lamport_amount - ); - return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into()); - } - - if stake.delegation.stake != MINIMUM_ACTIVE_STAKE { - msg!( - "Error: attempting to add stake with delegation of {} lamports, must have {} lamports", stake.delegation.stake, - MINIMUM_ACTIVE_STAKE ); return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into()); } @@ -970,7 +977,7 @@ impl Processor { stake_account_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, new_stake_authority_info.key, clock_info.clone(), stake_program_info.clone(), @@ -1097,7 +1104,7 @@ impl Processor { validator_stake_account_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, lamports, transient_stake_account_info.clone(), )?; @@ -1109,7 +1116,7 @@ impl Processor { withdraw_authority_info.clone(), stake_pool_info.key, AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, )?; validator_stake_info.active_stake_lamports = validator_stake_info @@ -1249,7 +1256,7 @@ impl Processor { reserve_stake_account_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, total_lamports, transient_stake_account_info.clone(), )?; @@ -1264,7 +1271,7 @@ impl Processor { withdraw_authority_info.clone(), stake_pool_info.key, AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, )?; validator_stake_info.transient_stake_lamports = total_lamports; @@ -1427,7 +1434,7 @@ impl Processor { transient_stake_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, reserve_stake_info.clone(), clock_info.clone(), stake_history_info.clone(), @@ -1453,7 +1460,7 @@ impl Processor { transient_stake_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, reserve_stake_info.clone(), clock_info.clone(), stake_history_info.clone(), @@ -1479,7 +1486,7 @@ impl Processor { transient_stake_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, validator_stake_info.clone(), clock_info.clone(), stake_history_info.clone(), @@ -1494,7 +1501,7 @@ impl Processor { validator_stake_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, reserve_stake_info.clone(), clock_info.clone(), stake_history_info.clone(), @@ -1623,7 +1630,7 @@ impl Processor { let reward_lamports = total_stake_lamports.saturating_sub(previous_lamports); let fee = stake_pool - .calc_fee_amount(reward_lamports) + .calc_epoch_fee_amount(reward_lamports) .ok_or(StakePoolError::CalculationFailure)?; if fee > 0 { @@ -1634,7 +1641,7 @@ impl Processor { manager_fee_info.clone(), withdraw_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, fee, )?; @@ -1718,17 +1725,19 @@ impl Processor { Ok(()) } - /// Processes [Deposit](enum.Instruction.html). - fn process_deposit(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + /// Processes [DepositStake](enum.Instruction.html). + fn process_deposit_stake(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { 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_authority_info = next_account_info(account_info_iter)?; + let stake_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 reserve_stake_account_info = next_account_info(account_info_iter)?; - let dest_user_info = next_account_info(account_info_iter)?; + let dest_user_pool_info = next_account_info(account_info_iter)?; + let manager_fee_info = next_account_info(account_info_iter)?; + let referrer_fee_info = next_account_info(account_info_iter)?; let pool_mint_info = next_account_info(account_info_iter)?; let clock_info = next_account_info(account_info_iter)?; let clock = &Clock::from_account_info(clock_info)?; @@ -1753,7 +1762,7 @@ impl Processor { program_id, stake_pool_info.key, )?; - stake_pool.check_deposit_authority(deposit_authority_info.key)?; + stake_pool.check_stake_deposit_authority(stake_deposit_authority_info.key)?; stake_pool.check_mint(pool_mint_info)?; stake_pool.check_validator_list(validator_list_info)?; stake_pool.check_reserve_stake(reserve_stake_account_info)?; @@ -1762,6 +1771,10 @@ impl Processor { return Err(ProgramError::IncorrectProgramId); } + if stake_pool.manager_fee_account != *manager_fee_info.key { + return Err(StakePoolError::InvalidFeeAccount.into()); + } + if stake_pool.last_update_epoch < clock.epoch { return Err(StakePoolError::StakeListAndPoolOutOfDate.into()); } @@ -1808,13 +1821,13 @@ impl Processor { msg!("Stake pre merge {}", validator_stake.delegation.stake); - let (deposit_authority_program_address, deposit_bump_seed) = + let (stake_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 { + if *stake_deposit_authority_info.key == stake_deposit_authority_program_address { Self::stake_authorize_signed( stake_pool_info.key, stake_info.clone(), - deposit_authority_info.clone(), + stake_deposit_authority_info.clone(), AUTHORITY_DEPOSIT, deposit_bump_seed, withdraw_authority_info.key, @@ -1824,7 +1837,7 @@ impl Processor { } else { Self::stake_authorize( stake_info.clone(), - deposit_authority_info.clone(), + stake_deposit_authority_info.clone(), withdraw_authority_info.key, clock_info.clone(), stake_program_info.clone(), @@ -1836,7 +1849,7 @@ impl Processor { stake_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, validator_stake_account_info.clone(), clock_info.clone(), stake_history_info.clone(), @@ -1854,24 +1867,71 @@ impl Processor { .stake .checked_sub(validator_stake.delegation.stake) .ok_or(StakePoolError::CalculationFailure)?; + let new_pool_tokens = stake_pool .calc_pool_tokens_for_deposit(all_deposit_lamports) .ok_or(StakePoolError::CalculationFailure)?; + let pool_tokens_stake_deposit_fee = stake_pool + .calc_pool_tokens_stake_deposit_fee(new_pool_tokens) + .ok_or(StakePoolError::CalculationFailure)?; + let pool_tokens_user = new_pool_tokens + .checked_sub(pool_tokens_stake_deposit_fee) + .ok_or(StakePoolError::CalculationFailure)?; + + let pool_tokens_referral_fee = stake_pool + .calc_pool_tokens_stake_referral_fee(pool_tokens_stake_deposit_fee) + .ok_or(StakePoolError::CalculationFailure)?; + let pool_tokens_manager_deposit_fee = pool_tokens_stake_deposit_fee + .checked_sub(pool_tokens_referral_fee) + .ok_or(StakePoolError::CalculationFailure)?; + + if pool_tokens_user + pool_tokens_manager_deposit_fee + pool_tokens_referral_fee + != new_pool_tokens + { + return Err(StakePoolError::CalculationFailure.into()); + } + if new_pool_tokens == 0 { return Err(StakePoolError::DepositTooSmall.into()); } - Self::token_mint_to( - stake_pool_info.key, - token_program_info.clone(), - pool_mint_info.clone(), - dest_user_info.clone(), - withdraw_authority_info.clone(), - AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, - new_pool_tokens, - )?; + if pool_tokens_user > 0 { + Self::token_mint_to( + stake_pool_info.key, + token_program_info.clone(), + pool_mint_info.clone(), + dest_user_pool_info.clone(), + withdraw_authority_info.clone(), + AUTHORITY_WITHDRAW, + stake_pool.stake_withdraw_bump_seed, + pool_tokens_user, + )?; + } + if pool_tokens_manager_deposit_fee > 0 { + Self::token_mint_to( + stake_pool_info.key, + token_program_info.clone(), + pool_mint_info.clone(), + manager_fee_info.clone(), + withdraw_authority_info.clone(), + AUTHORITY_WITHDRAW, + stake_pool.stake_withdraw_bump_seed, + pool_tokens_manager_deposit_fee, + )?; + } + if pool_tokens_referral_fee > 0 { + Self::token_mint_to( + stake_pool_info.key, + token_program_info.clone(), + pool_mint_info.clone(), + referrer_fee_info.clone(), + withdraw_authority_info.clone(), + AUTHORITY_WITHDRAW, + stake_pool.stake_withdraw_bump_seed, + pool_tokens_referral_fee, + )?; + } // withdraw additional lamports to the reserve let additional_lamports = all_deposit_lamports @@ -1883,7 +1943,7 @@ impl Processor { validator_stake_account_info.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, reserve_stake_account_info.clone(), clock_info.clone(), stake_history_info.clone(), @@ -1911,8 +1971,146 @@ impl Processor { Ok(()) } - /// Processes [Withdraw](enum.Instruction.html). - fn process_withdraw( + /// Processes [DepositStake](enum.Instruction.html). + fn process_deposit_sol( + program_id: &Pubkey, + accounts: &[AccountInfo], + deposit_lamports: u64, + ) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let stake_pool_info = next_account_info(account_info_iter)?; + let withdraw_authority_info = next_account_info(account_info_iter)?; + let reserve_stake_account_info = next_account_info(account_info_iter)?; + let from_user_lamports_info = next_account_info(account_info_iter)?; + let dest_user_pool_info = next_account_info(account_info_iter)?; + let manager_fee_info = next_account_info(account_info_iter)?; + let referrer_fee_info = next_account_info(account_info_iter)?; + let pool_mint_info = next_account_info(account_info_iter)?; + let clock_info = next_account_info(account_info_iter)?; + let clock = &Clock::from_account_info(clock_info)?; + let system_program_info = next_account_info(account_info_iter)?; + let token_program_info = next_account_info(account_info_iter)?; + let sol_deposit_authority_info = next_account_info(account_info_iter).ok(); + + check_account_owner(stake_pool_info, program_id)?; + let mut stake_pool = try_from_slice_unchecked::(&stake_pool_info.data.borrow())?; + if !stake_pool.is_valid() { + return Err(StakePoolError::InvalidState.into()); + } + + // Self::check_stake_activation(stake_info, clock, stake_history)?; + + stake_pool.check_authority_withdraw( + withdraw_authority_info.key, + program_id, + stake_pool_info.key, + )?; + if let Some(sol_deposit_authority) = sol_deposit_authority_info { + stake_pool.check_sol_deposit_authority(sol_deposit_authority)?; + } + stake_pool.check_mint(pool_mint_info)?; + stake_pool.check_reserve_stake(reserve_stake_account_info)?; + + if stake_pool.token_program_id != *token_program_info.key { + return Err(ProgramError::IncorrectProgramId); + } + check_system_program(system_program_info.key)?; + + if stake_pool.manager_fee_account != *manager_fee_info.key { + return Err(StakePoolError::InvalidFeeAccount.into()); + } + + // We want this to hold to ensure that deposit_sol mints pool tokens + // at the right price + if stake_pool.last_update_epoch < clock.epoch { + return Err(StakePoolError::StakeListAndPoolOutOfDate.into()); + } + + let new_pool_tokens = stake_pool + .calc_pool_tokens_for_deposit(deposit_lamports) + .ok_or(StakePoolError::CalculationFailure)?; + + let pool_tokens_sol_deposit_fee = stake_pool + .calc_pool_tokens_sol_deposit_fee(new_pool_tokens) + .ok_or(StakePoolError::CalculationFailure)?; + let pool_tokens_user = new_pool_tokens + .checked_sub(pool_tokens_sol_deposit_fee) + .ok_or(StakePoolError::CalculationFailure)?; + + let pool_tokens_referral_fee = stake_pool + .calc_pool_tokens_sol_referral_fee(pool_tokens_sol_deposit_fee) + .ok_or(StakePoolError::CalculationFailure)?; + let pool_tokens_manager_deposit_fee = pool_tokens_sol_deposit_fee + .checked_sub(pool_tokens_referral_fee) + .ok_or(StakePoolError::CalculationFailure)?; + + if pool_tokens_user + pool_tokens_manager_deposit_fee + pool_tokens_referral_fee + != new_pool_tokens + { + return Err(StakePoolError::CalculationFailure.into()); + } + + Self::sol_transfer( + from_user_lamports_info.clone(), + reserve_stake_account_info.clone(), + system_program_info.clone(), + deposit_lamports, + )?; + + if pool_tokens_user > 0 { + Self::token_mint_to( + stake_pool_info.key, + token_program_info.clone(), + pool_mint_info.clone(), + dest_user_pool_info.clone(), + withdraw_authority_info.clone(), + AUTHORITY_WITHDRAW, + stake_pool.stake_withdraw_bump_seed, + pool_tokens_user, + )?; + } + + if pool_tokens_manager_deposit_fee > 0 { + Self::token_mint_to( + stake_pool_info.key, + token_program_info.clone(), + pool_mint_info.clone(), + manager_fee_info.clone(), + withdraw_authority_info.clone(), + AUTHORITY_WITHDRAW, + stake_pool.stake_withdraw_bump_seed, + pool_tokens_manager_deposit_fee, + )?; + } + + if pool_tokens_referral_fee > 0 { + Self::token_mint_to( + stake_pool_info.key, + token_program_info.clone(), + pool_mint_info.clone(), + referrer_fee_info.clone(), + withdraw_authority_info.clone(), + AUTHORITY_WITHDRAW, + stake_pool.stake_withdraw_bump_seed, + pool_tokens_referral_fee, + )?; + } + + stake_pool.pool_token_supply = stake_pool + .pool_token_supply + .checked_add(new_pool_tokens) + .ok_or(StakePoolError::CalculationFailure)?; + stake_pool.total_stake_lamports = stake_pool + .total_stake_lamports + .checked_add(deposit_lamports) + .ok_or(StakePoolError::CalculationFailure)?; + stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?; + + Ok(()) + } + + /// Processes [WithdrawStake](enum.Instruction.html). + fn process_withdraw_stake( program_id: &Pubkey, accounts: &[AccountInfo], pool_tokens: u64, @@ -1925,7 +2123,7 @@ impl Processor { let stake_split_to = next_account_info(account_info_iter)?; let user_stake_authority_info = next_account_info(account_info_iter)?; let user_transfer_authority_info = next_account_info(account_info_iter)?; - let burn_from_info = next_account_info(account_info_iter)?; + let burn_from_pool_info = next_account_info(account_info_iter)?; let manager_fee_info = next_account_info(account_info_iter)?; let pool_mint_info = next_account_info(account_info_iter)?; let clock_info = next_account_info(account_info_iter)?; @@ -1967,7 +2165,7 @@ impl Processor { return Err(StakePoolError::InvalidState.into()); } - let pool_tokens_fee = if stake_pool.manager_fee_account == *burn_from_info.key { + let pool_tokens_fee = if stake_pool.manager_fee_account == *burn_from_pool_info.key { 0 } else { stake_pool @@ -2079,7 +2277,7 @@ impl Processor { Self::token_burn( token_program_info.clone(), - burn_from_info.clone(), + burn_from_pool_info.clone(), pool_mint_info.clone(), user_transfer_authority_info.clone(), pool_tokens_burnt, @@ -2090,7 +2288,7 @@ impl Processor { stake_split_from.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, withdraw_lamports, stake_split_to.clone(), )?; @@ -2100,7 +2298,7 @@ impl Processor { stake_split_to.clone(), withdraw_authority_info.clone(), AUTHORITY_WITHDRAW, - stake_pool.withdraw_bump_seed, + stake_pool.stake_withdraw_bump_seed, user_stake_authority_info.key, clock_info.clone(), stake_program_info.clone(), @@ -2109,7 +2307,7 @@ impl Processor { if pool_tokens_fee > 0 { Self::token_transfer( token_program_info.clone(), - burn_from_info.clone(), + burn_from_pool_info.clone(), manager_fee_info.clone(), user_transfer_authority_info.clone(), pool_tokens_fee, @@ -2175,7 +2373,11 @@ impl Processor { } /// Processes [SetFee](enum.Instruction.html). - fn process_set_fee(program_id: &Pubkey, accounts: &[AccountInfo], fee: Fee) -> ProgramResult { + fn process_set_fee( + program_id: &Pubkey, + accounts: &[AccountInfo], + fee: FeeType, + ) -> 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)?; @@ -2190,21 +2392,14 @@ impl Processor { stake_pool.check_manager(manager_info)?; - if stake_pool.last_update_epoch < clock.epoch { + if fee.can_only_change_next_epoch() && stake_pool.last_update_epoch < clock.epoch { return Err(StakePoolError::StakeListAndPoolOutOfDate.into()); } - // Numerator should be smaller than or equal to denominator (fee <= 1) - if fee.numerator > fee.denominator { - msg!( - "Fee greater than 100%, numerator {}, denominator {}", - fee.numerator, - fee.denominator - ); - return Err(StakePoolError::FeeTooHigh.into()); - } + fee.check_too_high()?; + fee.check_withdrawal(&stake_pool.withdrawal_fee)?; - stake_pool.next_epoch_fee = Some(fee); + stake_pool.update_fee(&fee); stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?; Ok(()) } @@ -2232,76 +2427,34 @@ impl Processor { Ok(()) } - /// Processes [SetWithdrawalFee](enum.Instruction.html). - fn process_set_withdrawal_fee( + /// Processes [SetStakeDepositAuthority/SetSolDepositAuthority](enum.Instruction.html). + fn process_set_deposit_authority( program_id: &Pubkey, accounts: &[AccountInfo], - fee: Fee, + deposit_type: DepositType, ) -> 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)?; - let clock_info = next_account_info(account_info_iter)?; - let clock = &Clock::from_account_info(clock_info)?; + + let new_sol_deposit_authority = next_account_info(account_info_iter).ok().map( + |new_sol_deposit_authority_account_info| *new_sol_deposit_authority_account_info.key, + ); check_account_owner(stake_pool_info, program_id)?; let mut stake_pool = try_from_slice_unchecked::(&stake_pool_info.data.borrow())?; if !stake_pool.is_valid() { return Err(StakePoolError::InvalidState.into()); } - stake_pool.check_manager(manager_info)?; - - if stake_pool.last_update_epoch < clock.epoch { - return Err(StakePoolError::StakeListAndPoolOutOfDate.into()); + match deposit_type { + DepositType::Stake => { + stake_pool.stake_deposit_authority = new_sol_deposit_authority.unwrap_or( + find_deposit_authority_program_address(program_id, stake_pool_info.key).0, + ); + } + DepositType::Sol => stake_pool.sol_deposit_authority = new_sol_deposit_authority, } - - // Numerator should be smaller than or equal to denominator (fee <= 1) - if fee.numerator > fee.denominator { - msg!( - "Fee greater than 100%, numerator {}, denominator {}", - fee.numerator, - fee.denominator - ); - return Err(StakePoolError::FeeTooHigh.into()); - } - - // If the previous withdrawal fee was 0, we allow the fee to be set to a - // maximum of (WITHDRAWAL_BASELINE_FEE * MAX_WITHDRAWAL_FEE_INCREASE) - let (old_num, old_denom) = if stake_pool.withdrawal_fee.denominator == 0 - || stake_pool.withdrawal_fee.numerator == 0 - { - ( - WITHDRAWAL_BASELINE_FEE.numerator, - WITHDRAWAL_BASELINE_FEE.denominator, - ) - } else { - ( - stake_pool.withdrawal_fee.numerator, - stake_pool.withdrawal_fee.denominator, - ) - }; - - // Check that new_fee / old_fee <= MAX_WITHDRAWAL_FEE_INCREASE - // Program fails if provided numerator or denominator is too large, resulting in overflow - if (old_num as u128) - .checked_mul(fee.denominator as u128) - .map(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.numerator as u128)) - .ok_or(StakePoolError::CalculationFailure)? - < (fee.numerator as u128) - .checked_mul(old_denom as u128) - .map(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.denominator as u128)) - .ok_or(StakePoolError::CalculationFailure)? - { - msg!( - "Fee increase exceeds maximum allowed, proposed increase factor ({} / {})", - fee.numerator * old_denom, - old_num * fee.denominator, - ); - return Err(StakePoolError::FeeIncreaseTooHigh.into()); - } - - stake_pool.next_withdrawal_fee = Some(fee); stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?; Ok(()) } @@ -2313,10 +2466,20 @@ impl Processor { StakePoolInstruction::Initialize { fee, withdrawal_fee, + deposit_fee, + referral_fee, max_validators, } => { msg!("Instruction: Initialize stake pool"); - Self::process_initialize(program_id, accounts, fee, withdrawal_fee, max_validators) + Self::process_initialize( + program_id, + accounts, + fee, + withdrawal_fee, + deposit_fee, + referral_fee, + max_validators, + ) } StakePoolInstruction::CreateValidatorStakeAccount => { msg!("Instruction: CreateValidatorStakeAccount"); @@ -2370,13 +2533,13 @@ impl Processor { msg!("Instruction: CleanupRemovedValidatorEntries"); Self::process_cleanup_removed_validator_entries(program_id, accounts) } - StakePoolInstruction::Deposit => { - msg!("Instruction: Deposit"); - Self::process_deposit(program_id, accounts) + StakePoolInstruction::DepositStake => { + msg!("Instruction: DepositStake"); + Self::process_deposit_stake(program_id, accounts) } - StakePoolInstruction::Withdraw(amount) => { - msg!("Instruction: Withdraw"); - Self::process_withdraw(program_id, accounts, amount) + StakePoolInstruction::WithdrawStake(amount) => { + msg!("Instruction: WithdrawStake"); + Self::process_withdraw_stake(program_id, accounts, amount) } StakePoolInstruction::SetManager => { msg!("Instruction: SetManager"); @@ -2390,9 +2553,13 @@ impl Processor { msg!("Instruction: SetStaker"); Self::process_set_staker(program_id, accounts) } - StakePoolInstruction::SetWithdrawalFee { fee } => { - msg!("Instruction: SetWithdrawalFee"); - Self::process_set_withdrawal_fee(program_id, accounts, fee) + StakePoolInstruction::DepositSol(lamports) => { + msg!("Instruction: DepositSol"); + Self::process_deposit_sol(program_id, accounts, lamports) + } + StakePoolInstruction::SetDepositAuthority(deposit_type) => { + msg!("Instruction: SetDepositAuthority"); + Self::process_set_deposit_authority(program_id, accounts, deposit_type) } } } @@ -2436,6 +2603,8 @@ impl PrintProgramError for StakePoolError { StakePoolError::FeeIncreaseTooHigh => msg!("Error: The fee cannot increase by a factor exceeding the stipulated ratio"), StakePoolError::WithdrawalTooSmall => msg!("Error: Not enough pool tokens provided to withdraw 1-lamport stake"), StakePoolError::DepositTooSmall => msg!("Error: Not enough lamports provided for deposit to result in one pool token"), + StakePoolError::InvalidStakeDepositAuthority => msg!("Error: Provided stake deposit authority does not match the program's"), + StakePoolError::InvalidSolDepositAuthority => msg!("Error: Provided sol deposit authority does not match the program's"), } } } diff --git a/stake-pool/program/src/state.rs b/stake-pool/program/src/state.rs index e765aafa..996c69bd 100644 --- a/stake-pool/program/src/state.rs +++ b/stake-pool/program/src/state.rs @@ -1,7 +1,10 @@ //! State transition types use { - crate::{big_vec::BigVec, error::StakePoolError, stake_program::Lockup}, + crate::{ + big_vec::BigVec, error::StakePoolError, stake_program::Lockup, MAX_WITHDRAWAL_FEE_INCREASE, + WITHDRAWAL_BASELINE_FEE, + }, borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, num_derive::FromPrimitive, num_traits::FromPrimitive, @@ -16,6 +19,7 @@ use { }, spl_math::checked_ceil_div::CheckedCeilDiv, std::convert::TryFrom, + std::{fmt, matches}, }; /// Enum representing the account type managed by the program @@ -49,7 +53,7 @@ pub struct StakePool { /// distribution pub staker: Pubkey, - /// Deposit authority + /// 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, @@ -58,11 +62,11 @@ pub struct StakePool { /// &[&stake_pool_address.to_bytes()[..32], b"deposit"], /// program_id, /// )` - pub deposit_authority: Pubkey, + pub stake_deposit_authority: Pubkey, - /// Withdrawal authority bump seed + /// Stake withdrawal authority bump seed /// for `create_program_address(&[state::StakePool account, "withdrawal"])` - pub withdraw_bump_seed: u8, + pub stake_withdraw_bump_seed: u8, /// Validator stake list storage account pub validator_list: Pubkey, @@ -105,8 +109,8 @@ pub struct StakePool { /// Preferred withdraw validator vote account pubkey pub preferred_withdraw_validator_vote_address: Option, - /// Fee assessed on deposits - pub deposit_fee: Fee, + /// Fee assessed on stake deposits + pub stake_deposit_fee: Fee, /// Fee assessed on withdrawals pub withdrawal_fee: Fee, @@ -114,14 +118,28 @@ pub struct StakePool { /// Future withdrawal fee, to be set for the following epoch pub next_withdrawal_fee: Option, - /// Fees paid out to referrers on referred deposits. + /// Fees paid out to referrers on referred stake deposits. /// Expressed as a percentage (0 - 100) of deposit fees. - /// i.e. `deposit_fee`% is collected as deposit fees for every deposit - /// and `referral_fee`% of the collected deposit fees is paid out to the referrer - pub referral_fee: u8, + /// 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 + /// the `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, } impl StakePool { /// calculate the pool tokens that should be minted for a deposit of `stake_lamports` + #[inline] pub fn calc_pool_tokens_for_deposit(&self, stake_lamports: u64) -> Option { if self.total_stake_lamports == 0 || self.pool_token_supply == 0 { return Some(stake_lamports); @@ -135,6 +153,7 @@ impl StakePool { } /// calculate lamports amount on withdrawal + #[inline] pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option { // `checked_ceil_div` returns `None` for a 0 quotient result, but in this // case, a return of 0 is valid for small amounts of pool tokens. So @@ -150,15 +169,51 @@ impl StakePool { } /// calculate pool tokens to be deducted as withdrawal fees + #[inline] pub fn calc_pool_tokens_withdrawal_fee(&self, pool_tokens: u64) -> Option { u64::try_from(self.withdrawal_fee.apply(pool_tokens)?).ok() } + /// calculate pool tokens to be deducted as stake deposit fees + #[inline] + pub fn calc_pool_tokens_stake_deposit_fee(&self, pool_tokens_minted: u64) -> Option { + u64::try_from(self.stake_deposit_fee.apply(pool_tokens_minted)?).ok() + } + + /// calculate pool tokens to be deducted from deposit fees as referral fees + #[inline] + pub fn calc_pool_tokens_stake_referral_fee(&self, stake_deposit_fee: u64) -> Option { + u64::try_from( + (stake_deposit_fee as u128) + .checked_mul(self.stake_referral_fee as u128)? + .checked_div(100u128)?, + ) + .ok() + } + + /// calculate pool tokens to be deducted as SOL deposit fees + #[inline] + pub fn calc_pool_tokens_sol_deposit_fee(&self, pool_tokens_minted: u64) -> Option { + u64::try_from(self.sol_deposit_fee.apply(pool_tokens_minted)?).ok() + } + + /// calculate pool tokens to be deducted from SOL deposit fees as referral fees + #[inline] + pub fn calc_pool_tokens_sol_referral_fee(&self, sol_deposit_fee: u64) -> Option { + u64::try_from( + (sol_deposit_fee as u128) + .checked_mul(self.sol_referral_fee as u128)? + .checked_div(100u128)?, + ) + .ok() + } + /// Calculate the fee in pool tokens that goes to the manager /// /// This function assumes that `reward_lamports` has not already been added /// to the stake pool's `total_stake_lamports` - pub fn calc_fee_amount(&self, reward_lamports: u64) -> Option { + #[inline] + pub fn calc_epoch_fee_amount(&self, reward_lamports: u64) -> Option { if reward_lamports == 0 { return Some(0); } @@ -178,7 +233,7 @@ impl StakePool { } /// Checks that the withdraw or deposit authority is valid - fn check_authority( + fn check_program_derived_authority( authority_address: &Pubkey, program_id: &Pubkey, stake_pool_address: &Pubkey, @@ -207,33 +262,55 @@ impl StakePool { } /// Checks that the withdraw authority is valid + #[inline] pub(crate) fn check_authority_withdraw( &self, withdraw_authority: &Pubkey, program_id: &Pubkey, stake_pool_address: &Pubkey, ) -> Result<(), ProgramError> { - Self::check_authority( + Self::check_program_derived_authority( withdraw_authority, program_id, stake_pool_address, crate::AUTHORITY_WITHDRAW, - self.withdraw_bump_seed, + self.stake_withdraw_bump_seed, ) } /// Checks that the deposit authority is valid - pub(crate) fn check_deposit_authority( + #[inline] + pub(crate) fn check_stake_deposit_authority( &self, - deposit_authority: &Pubkey, + stake_deposit_authority: &Pubkey, ) -> Result<(), ProgramError> { - if self.deposit_authority == *deposit_authority { + if self.stake_deposit_authority == *stake_deposit_authority { Ok(()) } else { - Err(StakePoolError::InvalidProgramAddress.into()) + Err(StakePoolError::InvalidStakeDepositAuthority.into()) } } + /// Checks that the deposit authority is valid + /// Does nothing if `sol_deposit_authority` is currently not set + #[inline] + pub(crate) fn check_sol_deposit_authority( + &self, + sol_deposit_authority: &AccountInfo, + ) -> Result<(), ProgramError> { + if let Some(auth) = self.sol_deposit_authority { + if auth != *sol_deposit_authority.key { + return Err(StakePoolError::InvalidSolDepositAuthority.into()); + } + if !sol_deposit_authority.is_signer { + msg!("SOL Deposit authority signature missing"); + return Err(StakePoolError::SignatureMissing.into()); + } + } + Ok(()) + } + /// Check staker validity and signature + #[inline] pub(crate) fn check_mint(&self, mint_info: &AccountInfo) -> Result<(), ProgramError> { if *mint_info.key != self.pool_mint { Err(StakePoolError::WrongPoolMint.into()) @@ -319,6 +396,18 @@ impl StakePool { pub fn is_uninitialized(&self) -> bool { self.account_type == AccountType::Uninitialized } + + /// Updates one of the StakePool's fees. + pub fn update_fee(&mut self, fee: &FeeType) { + match fee { + FeeType::SolReferral(new_fee) => self.sol_referral_fee = *new_fee, + FeeType::StakeReferral(new_fee) => self.stake_referral_fee = *new_fee, + FeeType::Epoch(new_fee) => self.next_epoch_fee = Some(*new_fee), + FeeType::Withdrawal(new_fee) => self.next_withdrawal_fee = Some(*new_fee), + FeeType::SolDeposit(new_fee) => self.sol_deposit_fee = *new_fee, + FeeType::StakeDeposit(new_fee) => self.stake_deposit_fee = *new_fee, + } + } } /// Storage list for all validator stake accounts in the pool. @@ -579,6 +668,96 @@ impl Fee { } } +impl fmt::Display for Fee { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.numerator, self.denominator) + } +} + +/// The type of fees that can be set on the stake pool +#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] +pub enum FeeType { + /// Referral fees for SOL deposits + SolReferral(u8), + /// Referral fees for stake deposits + StakeReferral(u8), + /// Management fee paid per epoch + Epoch(Fee), + /// Withdrawal fee + Withdrawal(Fee), + /// Deposit fee for SOL deposits + SolDeposit(Fee), + /// Deposit fee for stake deposits + StakeDeposit(Fee), +} + +impl FeeType { + /// Checks if the provided fee is too high, returning an error if so + pub fn check_too_high(&self) -> Result<(), StakePoolError> { + let too_high = match self { + Self::SolReferral(pct) => *pct > 100u8, + Self::StakeReferral(pct) => *pct > 100u8, + Self::Epoch(fee) => fee.numerator > fee.denominator, + Self::Withdrawal(fee) => fee.numerator > fee.denominator, + Self::SolDeposit(fee) => fee.numerator > fee.denominator, + Self::StakeDeposit(fee) => fee.numerator > fee.denominator, + }; + if too_high { + msg!("Fee greater than 100%: {:?}", self); + return Err(StakePoolError::FeeTooHigh); + } + Ok(()) + } + + /// Withdrawal fees have some additional restrictions, + /// this fn checks if those are met, returning an error if not. + /// Does nothing and returns Ok if fee type is not withdrawal + pub fn check_withdrawal(&self, old_withdrawal_fee: &Fee) -> Result<(), StakePoolError> { + let fee = match self { + Self::Withdrawal(fee) => fee, + _ => return Ok(()), + }; + + // If the previous withdrawal fee was 0, we allow the fee to be set to a + // maximum of (WITHDRAWAL_BASELINE_FEE * MAX_WITHDRAWAL_FEE_INCREASE) + let (old_num, old_denom) = + if old_withdrawal_fee.denominator == 0 || old_withdrawal_fee.numerator == 0 { + ( + WITHDRAWAL_BASELINE_FEE.numerator, + WITHDRAWAL_BASELINE_FEE.denominator, + ) + } else { + (old_withdrawal_fee.numerator, old_withdrawal_fee.denominator) + }; + + // Check that new_fee / old_fee <= MAX_WITHDRAWAL_FEE_INCREASE + // Program fails if provided numerator or denominator is too large, resulting in overflow + if (old_num as u128) + .checked_mul(fee.denominator as u128) + .map(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.numerator as u128)) + .ok_or(StakePoolError::CalculationFailure)? + < (fee.numerator as u128) + .checked_mul(old_denom as u128) + .map(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.denominator as u128)) + .ok_or(StakePoolError::CalculationFailure)? + { + msg!( + "Fee increase exceeds maximum allowed, proposed increase factor ({} / {})", + fee.numerator * old_denom, + old_num * fee.denominator, + ); + return Err(StakePoolError::FeeIncreaseTooHigh); + } + Ok(()) + } + + /// Returns if the contained fee can only be updated earliest on the next epoch + #[inline] + pub fn can_only_change_next_epoch(&self) -> bool { + matches!(self, Self::Withdrawal(_) | Self::Epoch(_)) + } +} + #[cfg(test)] mod test { use { @@ -779,7 +958,7 @@ mod test { ..StakePool::default() }; let reward_lamports = 10 * LAMPORTS_PER_SOL; - let pool_token_fee = stake_pool.calc_fee_amount(reward_lamports).unwrap(); + let pool_token_fee = stake_pool.calc_epoch_fee_amount(reward_lamports).unwrap(); stake_pool.total_stake_lamports += reward_lamports; stake_pool.pool_token_supply += pool_token_fee; @@ -815,7 +994,7 @@ mod test { ..StakePool::default() }; let rewards = 10; - let fee = stake_pool.calc_fee_amount(rewards).unwrap(); + let fee = stake_pool.calc_epoch_fee_amount(rewards).unwrap(); assert_eq!(fee, rewards); } @@ -832,7 +1011,7 @@ mod test { fee, ..StakePool::default() }; - let pool_token_fee = stake_pool.calc_fee_amount(reward_lamports).unwrap(); + let pool_token_fee = stake_pool.calc_epoch_fee_amount(reward_lamports).unwrap(); stake_pool.total_stake_lamports += reward_lamports; stake_pool.pool_token_supply += pool_token_fee; diff --git a/stake-pool/program/tests/decrease.rs b/stake-pool/program/tests/decrease.rs index f6a182fb..31530ace 100644 --- a/stake-pool/program/tests/decrease.rs +++ b/stake-pool/program/tests/decrease.rs @@ -41,7 +41,7 @@ async fn setup() -> ( ) .await; - let deposit_info = simple_deposit( + let deposit_info = simple_deposit_stake( &mut banks_client, &payer, &recent_blockhash, diff --git a/stake-pool/program/tests/deposit.rs b/stake-pool/program/tests/deposit.rs index 7308760a..03f1eb01 100644 --- a/stake-pool/program/tests/deposit.rs +++ b/stake-pool/program/tests/deposit.rs @@ -212,7 +212,9 @@ async fn success() { // Check minted tokens let user_token_balance = get_token_balance(&mut context.banks_client, &pool_token_account).await; - assert_eq!(user_token_balance, tokens_issued); + let tokens_issued_user = + tokens_issued - stake_pool_accounts.calculate_deposit_fee(tokens_issued); + assert_eq!(user_token_balance, tokens_issued_user); // Check balances in validator stake account list storage let validator_list = get_account( @@ -272,12 +274,14 @@ async fn fail_with_wrong_stake_program_id() { let accounts = vec![ AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false), - AccountMeta::new_readonly(stake_pool_accounts.deposit_authority, false), + AccountMeta::new_readonly(stake_pool_accounts.stake_deposit_authority, false), AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false), AccountMeta::new(deposit_stake, false), AccountMeta::new(validator_stake_account.stake_account, false), AccountMeta::new(stake_pool_accounts.reserve_stake.pubkey(), false), AccountMeta::new(pool_token_account, false), + AccountMeta::new(stake_pool_accounts.pool_fee_account.pubkey(), false), + AccountMeta::new(stake_pool_accounts.pool_fee_account.pubkey(), false), AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false), AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false), @@ -287,7 +291,7 @@ async fn fail_with_wrong_stake_program_id() { let instruction = Instruction { program_id: id(), accounts, - data: instruction::StakePoolInstruction::Deposit + data: instruction::StakePoolInstruction::DepositStake .try_to_vec() .unwrap(), }; @@ -325,7 +329,7 @@ async fn fail_with_wrong_token_program_id() { let wrong_token_program = Keypair::new(); let mut transaction = Transaction::new_with_payer( - &instruction::deposit( + &instruction::deposit_stake( &id(), &stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.validator_list.pubkey(), @@ -335,6 +339,8 @@ async fn fail_with_wrong_token_program_id() { &validator_stake_account.stake_account, &stake_pool_accounts.reserve_stake.pubkey(), &pool_token_account, + &stake_pool_accounts.pool_fee_account.pubkey(), + &stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.pool_mint.pubkey(), &wrong_token_program.pubkey(), ), @@ -567,7 +573,9 @@ async fn fail_with_wrong_mint_for_receiver_acc() { let program_error = token_error::TokenError::MintMismatch as u32; assert_eq!(error_index, program_error); } - _ => panic!("Wrong error occurs while try to deposit with wrong mint fro receiver account"), + _ => { + panic!("Wrong error occurs while try to deposit with wrong mint from receiver account") + } } } @@ -578,10 +586,11 @@ async fn fail_with_uninitialized_validator_list() {} // TODO async fn fail_with_out_of_dated_pool_balances() {} // TODO #[tokio::test] -async fn success_with_deposit_authority() { +async fn success_with_stake_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); + let stake_deposit_authority = Keypair::new(); + let stake_pool_accounts = + StakePoolAccounts::new_with_stake_deposit_authority(stake_deposit_authority); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) .await @@ -659,10 +668,11 @@ async fn success_with_deposit_authority() { } #[tokio::test] -async fn fail_without_deposit_authority_signature() { +async fn fail_without_stake_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); + let stake_deposit_authority = Keypair::new(); + let mut stake_pool_accounts = + StakePoolAccounts::new_with_stake_deposit_authority(stake_deposit_authority); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) .await @@ -726,8 +736,8 @@ async fn fail_without_deposit_authority_signature() { .unwrap(); let wrong_depositor = Keypair::new(); - stake_pool_accounts.deposit_authority = wrong_depositor.pubkey(); - stake_pool_accounts.deposit_authority_keypair = Some(wrong_depositor); + stake_pool_accounts.stake_deposit_authority = wrong_depositor.pubkey(); + stake_pool_accounts.stake_deposit_authority_keypair = Some(wrong_depositor); let error = stake_pool_accounts .deposit_stake( @@ -747,7 +757,7 @@ async fn fail_without_deposit_authority_signature() { TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { assert_eq!( error_index, - error::StakePoolError::InvalidProgramAddress as u32 + error::StakePoolError::InvalidStakeDepositAuthority as u32 ); } _ => panic!("Wrong error occurs while try to make a deposit with wrong stake program ID"), @@ -843,3 +853,113 @@ async fn fail_with_wrong_preferred_deposit() { _ => panic!("Wrong error occurs while try to make a deposit with wrong stake program ID"), } } + +#[tokio::test] +async fn success_with_referral_fee() { + let ( + mut context, + stake_pool_accounts, + validator_stake_account, + user, + deposit_stake, + pool_token_account, + stake_lamports, + ) = setup().await; + + let referrer = Keypair::new(); + let referrer_token_account = Keypair::new(); + create_token_account( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + &referrer_token_account, + &stake_pool_accounts.pool_mint.pubkey(), + &referrer.pubkey(), + ) + .await + .unwrap(); + + let referrer_balance_pre = + get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await; + + let mut transaction = Transaction::new_with_payer( + &instruction::deposit_stake( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.validator_list.pubkey(), + &stake_pool_accounts.withdraw_authority, + &deposit_stake, + &user.pubkey(), + &validator_stake_account.stake_account, + &stake_pool_accounts.reserve_stake.pubkey(), + &pool_token_account, + &stake_pool_accounts.pool_fee_account.pubkey(), + &referrer_token_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + ), + Some(&context.payer.pubkey()), + ); + transaction.sign(&[&context.payer, &user], context.last_blockhash); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + let referrer_balance_post = + get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await; + let referral_fee = stake_pool_accounts + .calculate_referral_fee(stake_pool_accounts.calculate_deposit_fee(stake_lamports)); + assert!(referral_fee > 0); + assert_eq!(referrer_balance_pre + referral_fee, referrer_balance_post); +} + +#[tokio::test] +async fn fail_with_invalid_referrer() { + let ( + mut context, + stake_pool_accounts, + validator_stake_account, + user, + deposit_stake, + pool_token_account, + _stake_lamports, + ) = setup().await; + + let invalid_token_account = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &instruction::deposit_stake( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.validator_list.pubkey(), + &stake_pool_accounts.withdraw_authority, + &deposit_stake, + &user.pubkey(), + &validator_stake_account.stake_account, + &stake_pool_accounts.reserve_stake.pubkey(), + &pool_token_account, + &stake_pool_accounts.pool_fee_account.pubkey(), + &invalid_token_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + ), + Some(&context.payer.pubkey()), + ); + transaction.sign(&[&context.payer, &user], context.last_blockhash); + let transaction_error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match transaction_error { + TransactionError::InstructionError(_, InstructionError::InvalidAccountData) => (), + _ => panic!( + "Wrong error occurs while try to make a deposit with an invalid referrer account" + ), + } +} diff --git a/stake-pool/program/tests/deposit_sol.rs b/stake-pool/program/tests/deposit_sol.rs new file mode 100644 index 00000000..f4c0bc65 --- /dev/null +++ b/stake-pool/program/tests/deposit_sol.rs @@ -0,0 +1,493 @@ +#![cfg(feature = "test-bpf")] + +mod helpers; + +use { + helpers::*, + solana_program::{ + borsh::try_from_slice_unchecked, instruction::InstructionError, pubkey::Pubkey, + }, + solana_program_test::*, + solana_sdk::{ + signature::{Keypair, Signer}, + transaction::Transaction, + transaction::TransactionError, + transport::TransportError, + }, + spl_stake_pool::{error, id, instruction, state}, + spl_token::error as token_error, +}; + +async fn setup() -> (ProgramTestContext, StakePoolAccounts, Keypair, Pubkey) { + let mut context = program_test().start_with_context().await; + + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + 1, + ) + .await + .unwrap(); + + let user = Keypair::new(); + + // make pool token account for user + let pool_token_account = Keypair::new(); + create_token_account( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + &pool_token_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + let mut transaction = Transaction::new_with_payer( + &[ + instruction::set_sol_deposit_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + stake_pool_accounts.deposit_fee, + ), + instruction::set_sol_referral_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + stake_pool_accounts.referral_fee, + ), + ], + Some(&context.payer.pubkey()), + ); + + transaction.sign( + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + ( + context, + stake_pool_accounts, + user, + pool_token_account.pubkey(), + ) +} + +#[tokio::test] +async fn success() { + let (mut context, stake_pool_accounts, _user, pool_token_account) = setup().await; + + // Save stake pool state before depositing + let pre_stake_pool = get_account( + &mut context.banks_client, + &stake_pool_accounts.stake_pool.pubkey(), + ) + .await; + let pre_stake_pool = + try_from_slice_unchecked::(&pre_stake_pool.data.as_slice()).unwrap(); + + // Save reserve state before depositing + let pre_reserve_lamports = get_account( + &mut context.banks_client, + &stake_pool_accounts.reserve_stake.pubkey(), + ) + .await + .lamports; + + let error = stake_pool_accounts + .deposit_sol( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + &pool_token_account, + TEST_STAKE_AMOUNT, + None, + ) + .await; + assert!(error.is_none()); + + let tokens_issued = TEST_STAKE_AMOUNT; // For now tokens are 1:1 to stake + + // Stake pool should add its balance to the pool balance + let post_stake_pool = get_account( + &mut context.banks_client, + &stake_pool_accounts.stake_pool.pubkey(), + ) + .await; + let post_stake_pool = + try_from_slice_unchecked::(&post_stake_pool.data.as_slice()).unwrap(); + assert_eq!( + post_stake_pool.total_stake_lamports, + pre_stake_pool.total_stake_lamports + TEST_STAKE_AMOUNT + ); + assert_eq!( + post_stake_pool.pool_token_supply, + pre_stake_pool.pool_token_supply + tokens_issued + ); + + // Check minted tokens + let user_token_balance = + get_token_balance(&mut context.banks_client, &pool_token_account).await; + let tokens_issued_user = + tokens_issued - stake_pool_accounts.calculate_deposit_fee(tokens_issued); + assert_eq!(user_token_balance, tokens_issued_user); + + // Check reserve + let post_reserve_lamports = get_account( + &mut context.banks_client, + &stake_pool_accounts.reserve_stake.pubkey(), + ) + .await + .lamports; + assert_eq!( + post_reserve_lamports, + pre_reserve_lamports + TEST_STAKE_AMOUNT + ); +} + +#[tokio::test] +async fn fail_with_wrong_token_program_id() { + let (mut context, stake_pool_accounts, _user, pool_token_account) = setup().await; + + let wrong_token_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &instruction::deposit_sol( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.withdraw_authority, + &stake_pool_accounts.reserve_stake.pubkey(), + &context.payer.pubkey(), + &pool_token_account, + &stake_pool_accounts.pool_fee_account.pubkey(), + &stake_pool_accounts.pool_fee_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &wrong_token_program.pubkey(), + TEST_STAKE_AMOUNT, + ), + Some(&context.payer.pubkey()), + ); + transaction.sign(&[&context.payer], context.last_blockhash); + let transaction_error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!("Wrong error occurs while try to make a deposit with wrong token program ID"), + } +} + +#[tokio::test] +async fn fail_with_wrong_withdraw_authority() { + let (mut context, mut stake_pool_accounts, _user, pool_token_account) = setup().await; + + stake_pool_accounts.withdraw_authority = Pubkey::new_unique(); + + let transaction_error = stake_pool_accounts + .deposit_sol( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + &pool_token_account, + TEST_STAKE_AMOUNT, + None, + ) + .await + .unwrap() + .unwrap(); + + match transaction_error { + 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 withdraw authority"), + } +} + +#[tokio::test] +async fn fail_with_wrong_mint_for_receiver_acc() { + let (mut context, stake_pool_accounts, _user, _pool_token_account) = setup().await; + + let outside_mint = Keypair::new(); + let outside_withdraw_auth = Keypair::new(); + let outside_manager = Keypair::new(); + let outside_pool_fee_acc = Keypair::new(); + + create_mint( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + &outside_mint, + &outside_withdraw_auth.pubkey(), + ) + .await + .unwrap(); + + create_token_account( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + &outside_pool_fee_acc, + &outside_mint.pubkey(), + &outside_manager.pubkey(), + ) + .await + .unwrap(); + + let transaction_error = stake_pool_accounts + .deposit_sol( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + &outside_pool_fee_acc.pubkey(), + TEST_STAKE_AMOUNT, + None, + ) + .await + .unwrap() + .unwrap(); + + match transaction_error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = token_error::TokenError::MintMismatch as u32; + assert_eq!(error_index, program_error); + } + _ => { + panic!("Wrong error occurs while try to deposit with wrong mint from receiver account") + } + } +} + +#[tokio::test] +async fn success_with_sol_deposit_authority() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) + .await + .unwrap(); + + let user = Keypair::new(); + + // 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 error = stake_pool_accounts + .deposit_sol( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account.pubkey(), + TEST_STAKE_AMOUNT, + None, + ) + .await; + assert!(error.is_none()); + + let sol_deposit_authority = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + Some(&sol_deposit_authority.pubkey()), + false, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let error = stake_pool_accounts + .deposit_sol( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account.pubkey(), + TEST_STAKE_AMOUNT, + Some(&sol_deposit_authority), + ) + .await; + assert!(error.is_none()); +} + +#[tokio::test] +async fn fail_without_sol_deposit_authority_signature() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let sol_deposit_authority = Keypair::new(); + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) + .await + .unwrap(); + + let user = Keypair::new(); + + // 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 mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + Some(&sol_deposit_authority.pubkey()), + false, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let wrong_depositor = Keypair::new(); + + let error = stake_pool_accounts + .deposit_sol( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account.pubkey(), + TEST_STAKE_AMOUNT, + Some(&wrong_depositor), + ) + .await + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + assert_eq!( + error_index, + error::StakePoolError::InvalidSolDepositAuthority as u32 + ); + } + _ => panic!("Wrong error occurs while trying to make a deposit without SOL deposit authority signature"), + } +} + +#[tokio::test] +async fn success_with_referral_fee() { + let (mut context, stake_pool_accounts, _user, pool_token_account) = setup().await; + + let referrer = Keypair::new(); + let referrer_token_account = Keypair::new(); + create_token_account( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + &referrer_token_account, + &stake_pool_accounts.pool_mint.pubkey(), + &referrer.pubkey(), + ) + .await + .unwrap(); + + let referrer_balance_pre = + get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await; + + let mut transaction = Transaction::new_with_payer( + &instruction::deposit_sol( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.withdraw_authority, + &stake_pool_accounts.reserve_stake.pubkey(), + &context.payer.pubkey(), + &pool_token_account, + &stake_pool_accounts.pool_fee_account.pubkey(), + &referrer_token_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + TEST_STAKE_AMOUNT, + ), + Some(&context.payer.pubkey()), + ); + transaction.sign(&[&context.payer], context.last_blockhash); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + let referrer_balance_post = + get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await; + let referral_fee = stake_pool_accounts + .calculate_referral_fee(stake_pool_accounts.calculate_deposit_fee(TEST_STAKE_AMOUNT)); + assert!(referral_fee > 0); + assert_eq!(referrer_balance_pre + referral_fee, referrer_balance_post); +} + +#[tokio::test] +async fn fail_with_invalid_referrer() { + let (mut context, stake_pool_accounts, _user, pool_token_account) = setup().await; + + let invalid_token_account = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &instruction::deposit_sol( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.withdraw_authority, + &stake_pool_accounts.reserve_stake.pubkey(), + &context.payer.pubkey(), + &pool_token_account, + &stake_pool_accounts.pool_fee_account.pubkey(), + &invalid_token_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + TEST_STAKE_AMOUNT, + ), + Some(&context.payer.pubkey()), + ); + transaction.sign(&[&context.payer], context.last_blockhash); + let transaction_error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match transaction_error { + TransactionError::InstructionError(_, InstructionError::InvalidAccountData) => (), + _ => panic!( + "Wrong error occurs while try to make a deposit with an invalid referrer account" + ), + } +} diff --git a/stake-pool/program/tests/helpers/mod.rs b/stake-pool/program/tests/helpers/mod.rs index 03675567..b1899334 100644 --- a/stake-pool/program/tests/helpers/mod.rs +++ b/stake-pool/program/tests/helpers/mod.rs @@ -266,9 +266,11 @@ pub async fn create_stake_pool( pool_token_account: &Pubkey, manager: &Keypair, staker: &Pubkey, - deposit_authority: &Option, + stake_deposit_authority: &Option, fee: &state::Fee, withdrawal_fee: &state::Fee, + deposit_fee: &state::Fee, + referral_fee: u8, max_validators: u32, ) -> Result<(), TransportError> { let rent = banks_client.get_rent().await.unwrap(); @@ -303,17 +305,19 @@ pub async fn create_stake_pool( pool_mint, pool_token_account, &spl_token::id(), - deposit_authority.as_ref().map(|k| k.pubkey()), + stake_deposit_authority.as_ref().map(|k| k.pubkey()), *fee, *withdrawal_fee, + *deposit_fee, + referral_fee, max_validators, ), ], Some(&payer.pubkey()), ); let mut signers = vec![payer, stake_pool, validator_list, manager]; - if let Some(deposit_authority) = deposit_authority.as_ref() { - signers.push(deposit_authority); + if let Some(stake_deposit_authority) = stake_deposit_authority.as_ref() { + signers.push(stake_deposit_authority); } transaction.sign(&signers, *recent_blockhash); banks_client.process_transaction(transaction).await?; @@ -542,10 +546,12 @@ pub struct StakePoolAccounts { pub manager: Keypair, pub staker: Keypair, pub withdraw_authority: Pubkey, - pub deposit_authority: Pubkey, - pub deposit_authority_keypair: Option, + pub stake_deposit_authority: Pubkey, + pub stake_deposit_authority_keypair: Option, pub fee: state::Fee, pub withdrawal_fee: state::Fee, + pub deposit_fee: state::Fee, + pub referral_fee: u8, pub max_validators: u32, } @@ -558,7 +564,7 @@ impl StakePoolAccounts { &[&stake_pool_address.to_bytes()[..32], b"withdraw"], &id(), ); - let (deposit_authority, _) = Pubkey::find_program_address( + let (stake_deposit_authority, _) = Pubkey::find_program_address( &[&stake_pool_address.to_bytes()[..32], b"deposit"], &id(), ); @@ -577,8 +583,8 @@ impl StakePoolAccounts { manager, staker, withdraw_authority, - deposit_authority, - deposit_authority_keypair: None, + stake_deposit_authority, + stake_deposit_authority_keypair: None, fee: state::Fee { numerator: 1, denominator: 100, @@ -587,14 +593,19 @@ impl StakePoolAccounts { numerator: 3, denominator: 1000, }, + deposit_fee: state::Fee { + numerator: 1, + denominator: 1000, + }, + referral_fee: 25, max_validators: MAX_TEST_VALIDATORS, } } - pub fn new_with_deposit_authority(deposit_authority: Keypair) -> Self { + pub fn new_with_stake_deposit_authority(stake_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.stake_deposit_authority = stake_deposit_authority.pubkey(); + stake_pool_accounts.stake_deposit_authority_keypair = Some(stake_deposit_authority); stake_pool_accounts } @@ -606,6 +617,14 @@ impl StakePoolAccounts { pool_tokens * self.withdrawal_fee.numerator / self.withdrawal_fee.denominator } + pub fn calculate_deposit_fee(&self, pool_tokens: u64) -> u64 { + pool_tokens * self.deposit_fee.numerator / self.deposit_fee.denominator + } + + pub fn calculate_referral_fee(&self, deposit_fee_collected: u64) -> u64 { + deposit_fee_collected * self.referral_fee as u64 / 100 + } + pub async fn initialize_stake_pool( &self, mut banks_client: &mut BanksClient, @@ -654,9 +673,11 @@ impl StakePoolAccounts { &self.pool_fee_account.pubkey(), &self.manager, &self.staker.pubkey(), - &self.deposit_authority_keypair, + &self.stake_deposit_authority_keypair, &self.fee, &self.withdrawal_fee, + &self.deposit_fee, + self.referral_fee, self.max_validators, ) .await?; @@ -675,36 +696,91 @@ impl StakePoolAccounts { current_staker: &Keypair, ) -> Option { 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( + let instructions = + if let Some(stake_deposit_authority) = self.stake_deposit_authority_keypair.as_ref() { + signers.push(stake_deposit_authority); + instruction::deposit_stake_with_authority( + &id(), + &self.stake_pool.pubkey(), + &self.validator_list.pubkey(), + &self.stake_deposit_authority, + &self.withdraw_authority, + stake, + ¤t_staker.pubkey(), + validator_stake_account, + &self.reserve_stake.pubkey(), + pool_account, + &self.pool_fee_account.pubkey(), + &self.pool_fee_account.pubkey(), + &self.pool_mint.pubkey(), + &spl_token::id(), + ) + } else { + instruction::deposit_stake( + &id(), + &self.stake_pool.pubkey(), + &self.validator_list.pubkey(), + &self.withdraw_authority, + stake, + ¤t_staker.pubkey(), + validator_stake_account, + &self.reserve_stake.pubkey(), + pool_account, + &self.pool_fee_account.pubkey(), + &self.pool_fee_account.pubkey(), + &self.pool_mint.pubkey(), + &spl_token::id(), + ) + }; + let transaction = Transaction::new_signed_with_payer( + &instructions, + Some(&payer.pubkey()), + &signers, + *recent_blockhash, + ); + banks_client.process_transaction(transaction).await.err() + } + + #[allow(clippy::too_many_arguments)] + pub async fn deposit_sol( + &self, + banks_client: &mut BanksClient, + payer: &Keypair, + recent_blockhash: &Hash, + pool_account: &Pubkey, + amount: u64, + sol_deposit_authority: Option<&Keypair>, + ) -> Option { + let mut signers = vec![payer]; + let instructions = if let Some(sol_deposit_authority) = sol_deposit_authority { + signers.push(sol_deposit_authority); + instruction::deposit_sol_with_authority( &id(), &self.stake_pool.pubkey(), - &self.validator_list.pubkey(), - &self.deposit_authority, + &sol_deposit_authority.pubkey(), &self.withdraw_authority, - stake, - ¤t_staker.pubkey(), - validator_stake_account, &self.reserve_stake.pubkey(), + &payer.pubkey(), pool_account, + &self.pool_fee_account.pubkey(), + &self.pool_fee_account.pubkey(), &self.pool_mint.pubkey(), &spl_token::id(), + amount, ) } else { - instruction::deposit( + instruction::deposit_sol( &id(), &self.stake_pool.pubkey(), - &self.validator_list.pubkey(), &self.withdraw_authority, - stake, - ¤t_staker.pubkey(), - validator_stake_account, &self.reserve_stake.pubkey(), + &payer.pubkey(), pool_account, + &self.pool_fee_account.pubkey(), + &self.pool_fee_account.pubkey(), &self.pool_mint.pubkey(), &spl_token::id(), + amount, ) }; let transaction = Transaction::new_signed_with_payer( @@ -730,7 +806,7 @@ impl StakePoolAccounts { amount: u64, ) -> Option { let transaction = Transaction::new_signed_with_payer( - &[instruction::withdraw( + &[instruction::withdraw_stake( &id(), &self.stake_pool.pubkey(), &self.validator_list.pubkey(), @@ -1084,7 +1160,7 @@ impl DepositStakeAccount { .await; } - pub async fn deposit( + pub async fn deposit_stake( &mut self, banks_client: &mut BanksClient, payer: &Keypair, @@ -1119,7 +1195,7 @@ impl DepositStakeAccount { } } -pub async fn simple_deposit( +pub async fn simple_deposit_stake( banks_client: &mut BanksClient, payer: &Keypair, recent_blockhash: &Hash, diff --git a/stake-pool/program/tests/huge_pool.rs b/stake-pool/program/tests/huge_pool.rs index 84925443..baa5b1d1 100644 --- a/stake-pool/program/tests/huge_pool.rs +++ b/stake-pool/program/tests/huge_pool.rs @@ -32,7 +32,7 @@ use { spl_token::state::{Account as SplAccount, AccountState as SplAccountState, Mint}, }; -const HUGE_POOL_SIZE: u32 = 4_000; +const HUGE_POOL_SIZE: u32 = 3_950; const ACCOUNT_RENT_EXEMPTION: u64 = 1_000_000_000; // go with something big to be safe const STAKE_AMOUNT: u64 = 200_000_000_000; const STAKE_ACCOUNT_RENT_EXEMPTION: u64 = 2_282_880; @@ -56,15 +56,15 @@ async fn setup( stake_pool_accounts.max_validators = max_validators; let stake_pool_pubkey = stake_pool_accounts.stake_pool.pubkey(); - let (_, withdraw_bump_seed) = + let (_, stake_withdraw_bump_seed) = find_withdraw_authority_program_address(&id(), &stake_pool_pubkey); let mut stake_pool = StakePool { account_type: AccountType::StakePool, manager: stake_pool_accounts.manager.pubkey(), staker: stake_pool_accounts.staker.pubkey(), - deposit_authority: stake_pool_accounts.deposit_authority, - withdraw_bump_seed, + stake_deposit_authority: stake_pool_accounts.stake_deposit_authority, + stake_withdraw_bump_seed, validator_list: stake_pool_accounts.validator_list.pubkey(), reserve_stake: stake_pool_accounts.reserve_stake.pubkey(), pool_mint: stake_pool_accounts.pool_mint.pubkey(), @@ -78,10 +78,13 @@ async fn setup( next_epoch_fee: None, preferred_deposit_validator_vote_address: None, preferred_withdraw_validator_vote_address: None, - deposit_fee: Fee::default(), + stake_deposit_fee: Fee::default(), + sol_deposit_fee: Fee::default(), withdrawal_fee: Fee::default(), next_withdrawal_fee: None, - referral_fee: 0, + stake_referral_fee: 0, + sol_referral_fee: 0, + sol_deposit_authority: None, }; let mut validator_list = ValidatorList::new(max_validators); @@ -592,7 +595,7 @@ async fn add_validator_to_pool() { increase_amount, ) .await; - assert!(error.is_none()); + assert!(error.is_none(), "{:?}", error); let validator_list = get_account( &mut context.banks_client, @@ -655,7 +658,7 @@ async fn set_preferred() { } #[tokio::test] -async fn deposit() { +async fn deposit_stake() { let (mut context, stake_pool_accounts, _, vote_pubkey, user, stake_pubkey, pool_account_pubkey) = setup(HUGE_POOL_SIZE, HUGE_POOL_SIZE, STAKE_AMOUNT).await; diff --git a/stake-pool/program/tests/increase.rs b/stake-pool/program/tests/increase.rs index b6727352..bd091c33 100644 --- a/stake-pool/program/tests/increase.rs +++ b/stake-pool/program/tests/increase.rs @@ -46,7 +46,7 @@ async fn setup() -> ( ) .await; - let _deposit_info = simple_deposit( + let _deposit_info = simple_deposit_stake( &mut banks_client, &payer, &recent_blockhash, diff --git a/stake-pool/program/tests/initialize.rs b/stake-pool/program/tests/initialize.rs index d6cb05a3..6d028c27 100644 --- a/stake-pool/program/tests/initialize.rs +++ b/stake-pool/program/tests/initialize.rs @@ -254,6 +254,8 @@ async fn fail_with_wrong_max_validators() { None, stake_pool_accounts.fee, stake_pool_accounts.withdrawal_fee, + stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ), ], @@ -325,6 +327,8 @@ async fn fail_with_wrong_mint_authority() { &None, &stake_pool_accounts.fee, &stake_pool_accounts.withdrawal_fee, + &stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ) .await @@ -411,6 +415,8 @@ async fn fail_with_freeze_authority() { &None, &stake_pool_accounts.fee, &stake_pool_accounts.withdrawal_fee, + &stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ) .await @@ -499,6 +505,8 @@ async fn fail_with_wrong_token_program_id() { None, stake_pool_accounts.fee, stake_pool_accounts.withdrawal_fee, + stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ), ], @@ -576,6 +584,8 @@ async fn fail_with_wrong_fee_account() { &None, &stake_pool_accounts.fee, &stake_pool_accounts.withdrawal_fee, + &stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ) .await @@ -665,6 +675,8 @@ async fn fail_with_not_rent_exempt_pool() { None, stake_pool_accounts.fee, stake_pool_accounts.withdrawal_fee, + stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ), ], @@ -740,6 +752,8 @@ async fn fail_with_not_rent_exempt_validator_list() { None, stake_pool_accounts.fee, stake_pool_accounts.withdrawal_fee, + stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ), ], @@ -792,6 +806,8 @@ async fn fail_without_manager_signature() { let init_data = instruction::StakePoolInstruction::Initialize { fee: stake_pool_accounts.fee, withdrawal_fee: stake_pool_accounts.withdrawal_fee, + deposit_fee: stake_pool_accounts.deposit_fee, + referral_fee: stake_pool_accounts.referral_fee, max_validators: stake_pool_accounts.max_validators, }; let data = init_data.try_to_vec().unwrap(); @@ -914,6 +930,8 @@ async fn fail_with_pre_minted_pool_tokens() { &None, &stake_pool_accounts.fee, &stake_pool_accounts.withdrawal_fee, + &stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ) .await @@ -976,6 +994,8 @@ async fn fail_with_bad_reserve() { &None, &stake_pool_accounts.fee, &stake_pool_accounts.withdrawal_fee, + &stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ) .await @@ -1022,6 +1042,8 @@ async fn fail_with_bad_reserve() { &None, &stake_pool_accounts.fee, &stake_pool_accounts.withdrawal_fee, + &stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ) .await @@ -1071,6 +1093,8 @@ async fn fail_with_bad_reserve() { &None, &stake_pool_accounts.fee, &stake_pool_accounts.withdrawal_fee, + &stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ) .await @@ -1120,6 +1144,8 @@ async fn fail_with_bad_reserve() { &None, &stake_pool_accounts.fee, &stake_pool_accounts.withdrawal_fee, + &stake_pool_accounts.deposit_fee, + stake_pool_accounts.referral_fee, stake_pool_accounts.max_validators, ) .await @@ -1138,10 +1164,11 @@ async fn fail_with_bad_reserve() { } #[tokio::test] -async fn success_with_required_deposit_authority() { +async fn success_with_required_stake_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); + let stake_deposit_authority = Keypair::new(); + let stake_pool_accounts = + StakePoolAccounts::new_with_stake_deposit_authority(stake_deposit_authority); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) .await @@ -1153,7 +1180,7 @@ async fn success_with_required_deposit_authority() { let stake_pool = try_from_slice_unchecked::(stake_pool_account.data.as_slice()).unwrap(); assert_eq!( - stake_pool.deposit_authority, - stake_pool_accounts.deposit_authority + stake_pool.stake_deposit_authority, + stake_pool_accounts.stake_deposit_authority ); } diff --git a/stake-pool/program/tests/set_deposit_authority.rs b/stake-pool/program/tests/set_deposit_authority.rs new file mode 100644 index 00000000..ddded681 --- /dev/null +++ b/stake-pool/program/tests/set_deposit_authority.rs @@ -0,0 +1,359 @@ +#![cfg(feature = "test-bpf")] + +mod helpers; + +use { + borsh::BorshSerialize, + helpers::*, + solana_program::{ + borsh::try_from_slice_unchecked, + hash::Hash, + instruction::{AccountMeta, Instruction}, + }, + solana_program_test::*, + solana_sdk::{ + instruction::InstructionError, signature::Keypair, signature::Signer, + transaction::Transaction, transaction::TransactionError, transport::TransportError, + }, + spl_stake_pool::{error, find_deposit_authority_program_address, id, instruction, state}, +}; + +async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, Keypair) { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) + .await + .unwrap(); + + let new_deposit_authority = Keypair::new(); + + ( + banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + new_deposit_authority, + ) +} + +#[tokio::test] +async fn success_set_stake_deposit_authority() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + new_stake_deposit_authority, + ) = setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + Some(&new_stake_deposit_authority.pubkey()), + true, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; + let stake_pool = + try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + + assert_eq!( + stake_pool.stake_deposit_authority, + new_stake_deposit_authority.pubkey() + ); +} + +#[tokio::test] +async fn success_set_stake_deposit_authority_to_none() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + new_stake_deposit_authority, + ) = setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + Some(&new_stake_deposit_authority.pubkey()), + true, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; + let stake_pool = + try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + + assert_eq!( + stake_pool.stake_deposit_authority, + new_stake_deposit_authority.pubkey() + ); + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + None, + true, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; + let stake_pool = + try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + + assert_eq!( + stake_pool.stake_deposit_authority, + find_deposit_authority_program_address(&id(), &stake_pool_accounts.stake_pool.pubkey()).0 + ); +} + +#[tokio::test] +async fn fail_stake_wrong_manager() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + new_stake_deposit_authority, + ) = setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &new_stake_deposit_authority.pubkey(), + Some(&new_stake_deposit_authority.pubkey()), + true, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &new_stake_deposit_authority], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongManager as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while malicious try to set manager"), + } +} + +#[tokio::test] +async fn fail_set_stake_deposit_authority_without_signature() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + new_stake_deposit_authority, + ) = setup().await; + + let data = + instruction::StakePoolInstruction::SetDepositAuthority(instruction::DepositType::Stake) + .try_to_vec() + .unwrap(); + let accounts = vec![ + AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.manager.pubkey(), false), + AccountMeta::new_readonly(new_stake_deposit_authority.pubkey(), false), + ]; + let instruction = Instruction { + program_id: id(), + accounts, + data, + }; + + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::SignatureMissing as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to set new manager without signature"), + } +} + +#[tokio::test] +async fn success_set_sol_deposit_authority() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_sol_deposit_authority) = + setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + Some(&new_sol_deposit_authority.pubkey()), + false, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; + let stake_pool = + try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + + assert_eq!( + stake_pool.sol_deposit_authority, + Some(new_sol_deposit_authority.pubkey()) + ); +} + +#[tokio::test] +async fn success_set_sol_deposit_authority_to_none() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_sol_deposit_authority) = + setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + Some(&new_sol_deposit_authority.pubkey()), + false, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; + let stake_pool = + try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + + assert_eq!( + stake_pool.sol_deposit_authority, + Some(new_sol_deposit_authority.pubkey()) + ); + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + None, + false, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; + let stake_pool = + try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + + assert_eq!(stake_pool.sol_deposit_authority, None); +} + +#[tokio::test] +async fn fail_sol_wrong_manager() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_sol_deposit_authority) = + setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_deposit_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &new_sol_deposit_authority.pubkey(), + Some(&new_sol_deposit_authority.pubkey()), + false, + )], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &new_sol_deposit_authority], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongManager as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while malicious try to set manager"), + } +} + +#[tokio::test] +async fn fail_set_sol_deposit_authority_without_signature() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_sol_deposit_authority) = + setup().await; + + let data = + instruction::StakePoolInstruction::SetDepositAuthority(instruction::DepositType::Sol) + .try_to_vec() + .unwrap(); + let accounts = vec![ + AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.manager.pubkey(), false), + AccountMeta::new_readonly(new_sol_deposit_authority.pubkey(), false), + ]; + let instruction = Instruction { + program_id: id(), + accounts, + data, + }; + + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::SignatureMissing as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to set new manager without signature"), + } +} diff --git a/stake-pool/program/tests/set_deposit_fee.rs b/stake-pool/program/tests/set_deposit_fee.rs new file mode 100644 index 00000000..f99f7cc9 --- /dev/null +++ b/stake-pool/program/tests/set_deposit_fee.rs @@ -0,0 +1,274 @@ +#![cfg(feature = "test-bpf")] + +mod helpers; + +use { + helpers::*, + solana_program_test::*, + solana_sdk::{ + borsh::try_from_slice_unchecked, + instruction::InstructionError, + signature::{Keypair, Signer}, + transaction::{Transaction, TransactionError}, + }, + spl_stake_pool::{error, id, instruction, state::Fee, state::StakePool}, +}; + +async fn setup(fee: Option) -> (ProgramTestContext, StakePoolAccounts, Fee) { + let mut context = program_test().start_with_context().await; + let mut stake_pool_accounts = StakePoolAccounts::new(); + if let Some(fee) = fee { + stake_pool_accounts.deposit_fee = fee; + } + stake_pool_accounts + .initialize_stake_pool( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + 1, + ) + .await + .unwrap(); + let new_deposit_fee = Fee { + numerator: 823, + denominator: 1000, + }; + + (context, stake_pool_accounts, new_deposit_fee) +} + +#[tokio::test] +async fn success_stake() { + let (mut context, stake_pool_accounts, new_deposit_fee) = setup(None).await; + + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_stake_deposit_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_deposit_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + let stake_pool = get_account( + &mut context.banks_client, + &stake_pool_accounts.stake_pool.pubkey(), + ) + .await; + let stake_pool = try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + assert_eq!(stake_pool.stake_deposit_fee, new_deposit_fee); +} + +#[tokio::test] +async fn success_stake_increase_fee_from_0() { + let (mut context, stake_pool_accounts, _) = setup(Some(Fee { + numerator: 0, + denominator: 0, + })) + .await; + let new_deposit_fee = Fee { + numerator: 324, + denominator: 1234, + }; + + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_stake_deposit_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_deposit_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + let stake_pool = get_account( + &mut context.banks_client, + &stake_pool_accounts.stake_pool.pubkey(), + ) + .await; + let stake_pool = try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + assert_eq!(stake_pool.stake_deposit_fee, new_deposit_fee); +} + +#[tokio::test] +async fn fail_stake_wrong_manager() { + let (mut context, stake_pool_accounts, new_deposit_fee) = setup(None).await; + + let wrong_manager = Keypair::new(); + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_stake_deposit_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &wrong_manager.pubkey(), + new_deposit_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &wrong_manager], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = error::StakePoolError::WrongManager as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while signing with the wrong manager"), + } +} + +#[tokio::test] +async fn fail_stake_high_deposit_fee() { + let (mut context, stake_pool_accounts, _new_deposit_fee) = setup(None).await; + + let new_deposit_fee = Fee { + numerator: 100001, + denominator: 100000, + }; + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_stake_deposit_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_deposit_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = error::StakePoolError::FeeTooHigh as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs when setting fee too high"), + } +} + +#[tokio::test] +async fn success_sol() { + let (mut context, stake_pool_accounts, new_deposit_fee) = setup(None).await; + + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_sol_deposit_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_deposit_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + let stake_pool = get_account( + &mut context.banks_client, + &stake_pool_accounts.stake_pool.pubkey(), + ) + .await; + let stake_pool = try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + assert_eq!(stake_pool.sol_deposit_fee, new_deposit_fee); +} + +#[tokio::test] +async fn fail_sol_wrong_manager() { + let (mut context, stake_pool_accounts, new_deposit_fee) = setup(None).await; + + let wrong_manager = Keypair::new(); + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_sol_deposit_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &wrong_manager.pubkey(), + new_deposit_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &wrong_manager], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = error::StakePoolError::WrongManager as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while signing with the wrong manager"), + } +} + +#[tokio::test] +async fn fail_sol_high_deposit_fee() { + let (mut context, stake_pool_accounts, _new_deposit_fee) = setup(None).await; + + let new_deposit_fee = Fee { + numerator: 100001, + denominator: 100000, + }; + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_sol_deposit_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_deposit_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = error::StakePoolError::FeeTooHigh as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs when setting fee too high"), + } +} diff --git a/stake-pool/program/tests/set_fee.rs b/stake-pool/program/tests/set_fee.rs index fb5adb58..00585ad8 100644 --- a/stake-pool/program/tests/set_fee.rs +++ b/stake-pool/program/tests/set_fee.rs @@ -50,7 +50,7 @@ async fn success() { let old_fee = stake_pool.fee; let transaction = Transaction::new_signed_with_payer( - &[instruction::set_fee( + &[instruction::set_epoch_fee( &id(), &stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.manager.pubkey(), @@ -108,7 +108,7 @@ async fn fail_wrong_manager() { let wrong_manager = Keypair::new(); let transaction = Transaction::new_signed_with_payer( - &[instruction::set_fee( + &[instruction::set_epoch_fee( &id(), &stake_pool_accounts.stake_pool.pubkey(), &wrong_manager.pubkey(), @@ -144,7 +144,7 @@ async fn fail_high_fee() { denominator: 10, }; let transaction = Transaction::new_signed_with_payer( - &[instruction::set_fee( + &[instruction::set_epoch_fee( &id(), &stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.manager.pubkey(), @@ -193,7 +193,7 @@ async fn fail_not_updated() { context.warp_to_slot(50_000).unwrap(); let transaction = Transaction::new_signed_with_payer( - &[instruction::set_fee( + &[instruction::set_epoch_fee( &id(), &stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.manager.pubkey(), diff --git a/stake-pool/program/tests/set_referral_fee.rs b/stake-pool/program/tests/set_referral_fee.rs new file mode 100644 index 00000000..cda1a7b4 --- /dev/null +++ b/stake-pool/program/tests/set_referral_fee.rs @@ -0,0 +1,258 @@ +#![cfg(feature = "test-bpf")] + +mod helpers; + +use { + helpers::*, + solana_program_test::*, + solana_sdk::{ + borsh::try_from_slice_unchecked, + instruction::InstructionError, + signature::{Keypair, Signer}, + transaction::{Transaction, TransactionError}, + }, + spl_stake_pool::{error, id, instruction, state::StakePool}, +}; + +async fn setup(fee: Option) -> (ProgramTestContext, StakePoolAccounts, u8) { + let mut context = program_test().start_with_context().await; + let mut stake_pool_accounts = StakePoolAccounts::new(); + if let Some(fee) = fee { + stake_pool_accounts.referral_fee = fee; + } + stake_pool_accounts + .initialize_stake_pool( + &mut context.banks_client, + &context.payer, + &context.last_blockhash, + 1, + ) + .await + .unwrap(); + let new_referral_fee = 15u8; + + (context, stake_pool_accounts, new_referral_fee) +} + +#[tokio::test] +async fn success_stake() { + let (mut context, stake_pool_accounts, new_referral_fee) = setup(None).await; + + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_stake_referral_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_referral_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + let stake_pool = get_account( + &mut context.banks_client, + &stake_pool_accounts.stake_pool.pubkey(), + ) + .await; + let stake_pool = try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + assert_eq!(stake_pool.stake_referral_fee, new_referral_fee); +} + +#[tokio::test] +async fn success_stake_increase_fee_from_0() { + let (mut context, stake_pool_accounts, _) = setup(Some(0u8)).await; + let new_referral_fee = 30u8; + + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_stake_referral_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_referral_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + let stake_pool = get_account( + &mut context.banks_client, + &stake_pool_accounts.stake_pool.pubkey(), + ) + .await; + let stake_pool = try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + assert_eq!(stake_pool.stake_referral_fee, new_referral_fee); +} + +#[tokio::test] +async fn fail_stake_wrong_manager() { + let (mut context, stake_pool_accounts, new_referral_fee) = setup(None).await; + + let wrong_manager = Keypair::new(); + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_stake_referral_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &wrong_manager.pubkey(), + new_referral_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &wrong_manager], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = error::StakePoolError::WrongManager as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while signing with the wrong manager"), + } +} + +#[tokio::test] +async fn fail_stake_high_referral_fee() { + let (mut context, stake_pool_accounts, _new_referral_fee) = setup(None).await; + + let new_referral_fee = 110u8; + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_stake_referral_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_referral_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = error::StakePoolError::FeeTooHigh as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs when setting fee too high"), + } +} + +#[tokio::test] +async fn success_sol() { + let (mut context, stake_pool_accounts, new_referral_fee) = setup(None).await; + + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_sol_referral_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_referral_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + context + .banks_client + .process_transaction(transaction) + .await + .unwrap(); + + let stake_pool = get_account( + &mut context.banks_client, + &stake_pool_accounts.stake_pool.pubkey(), + ) + .await; + let stake_pool = try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); + assert_eq!(stake_pool.sol_referral_fee, new_referral_fee); +} + +#[tokio::test] +async fn fail_sol_wrong_manager() { + let (mut context, stake_pool_accounts, new_referral_fee) = setup(None).await; + + let wrong_manager = Keypair::new(); + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_sol_referral_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &wrong_manager.pubkey(), + new_referral_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &wrong_manager], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = error::StakePoolError::WrongManager as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while signing with the wrong manager"), + } +} + +#[tokio::test] +async fn fail_sol_high_referral_fee() { + let (mut context, stake_pool_accounts, _new_referral_fee) = setup(None).await; + + let new_referral_fee = 110u8; + let transaction = Transaction::new_signed_with_payer( + &[instruction::set_sol_referral_fee( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.manager.pubkey(), + new_referral_fee, + )], + Some(&context.payer.pubkey()), + &[&context.payer, &stake_pool_accounts.manager], + context.last_blockhash, + ); + let error = context + .banks_client + .process_transaction(transaction) + .await + .err() + .unwrap() + .unwrap(); + + match error { + TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { + let program_error = error::StakePoolError::FeeTooHigh as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs when setting fee too high"), + } +} diff --git a/stake-pool/program/tests/set_withdrawal_fee.rs b/stake-pool/program/tests/set_withdrawal_fee.rs index fac2ce4b..395f3f9d 100644 --- a/stake-pool/program/tests/set_withdrawal_fee.rs +++ b/stake-pool/program/tests/set_withdrawal_fee.rs @@ -321,42 +321,6 @@ async fn fail_high_withdrawal_fee_increase_from_0() { } } -#[tokio::test] -async fn fail_bad_fee() { - let (mut context, stake_pool_accounts, _new_fee) = setup(None).await; - - let new_withdrawal_fee = Fee { - numerator: 11, - denominator: 10, - }; - let transaction = Transaction::new_signed_with_payer( - &[instruction::set_withdrawal_fee( - &id(), - &stake_pool_accounts.stake_pool.pubkey(), - &stake_pool_accounts.manager.pubkey(), - new_withdrawal_fee, - )], - Some(&context.payer.pubkey()), - &[&context.payer, &stake_pool_accounts.manager], - context.last_blockhash, - ); - let error = context - .banks_client - .process_transaction(transaction) - .await - .err() - .unwrap() - .unwrap(); - - match error { - TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { - let program_error = error::StakePoolError::FeeTooHigh as u32; - assert_eq!(error_index, program_error); - } - _ => panic!("Wrong error occurs when setting fee too high"), - } -} - #[tokio::test] async fn fail_not_updated() { let mut context = program_test().start_with_context().await; diff --git a/stake-pool/program/tests/update_stake_pool_balance.rs b/stake-pool/program/tests/update_stake_pool_balance.rs index 98b9f87f..aa1a2272 100644 --- a/stake-pool/program/tests/update_stake_pool_balance.rs +++ b/stake-pool/program/tests/update_stake_pool_balance.rs @@ -44,7 +44,7 @@ async fn setup() -> ( ) .await; - let _deposit_info = simple_deposit( + let _deposit_info = simple_deposit_stake( &mut context.banks_client, &context.payer, &context.last_blockhash, @@ -65,6 +65,12 @@ async fn setup() -> ( async fn success() { let (mut context, stake_pool_accounts, stake_accounts) = setup().await; + let pre_fee = get_token_balance( + &mut context.banks_client, + &stake_pool_accounts.pool_fee_account.pubkey(), + ) + .await; + let pre_balance = get_validator_list_sum( &mut context.banks_client, &stake_pool_accounts.reserve_stake.pubkey(), @@ -134,7 +140,7 @@ async fn success() { let stake_pool = try_from_slice_unchecked::(&stake_pool.data.as_slice()).unwrap(); assert_eq!(post_balance, stake_pool.total_stake_lamports); - let actual_fee = get_token_balance( + let post_fee = get_token_balance( &mut context.banks_client, &stake_pool_accounts.pool_fee_account.pubkey(), ) @@ -144,6 +150,7 @@ async fn success() { &stake_pool_accounts.pool_mint.pubkey(), ) .await; + let actual_fee = post_fee - pre_fee; assert_eq!(pool_token_supply - pre_token_supply, actual_fee); let expected_fee_lamports = diff --git a/stake-pool/program/tests/update_validator_list_balance.rs b/stake-pool/program/tests/update_validator_list_balance.rs index c8326183..f761aa0f 100644 --- a/stake-pool/program/tests/update_validator_list_balance.rs +++ b/stake-pool/program/tests/update_validator_list_balance.rs @@ -103,7 +103,7 @@ async fn setup( for deposit_account in &mut deposit_accounts { deposit_account - .deposit( + .deposit_stake( &mut context.banks_client, &context.payer, &context.last_blockhash, diff --git a/stake-pool/program/tests/vsa_remove.rs b/stake-pool/program/tests/vsa_remove.rs index 743cf888..c27d9c54 100644 --- a/stake-pool/program/tests/vsa_remove.rs +++ b/stake-pool/program/tests/vsa_remove.rs @@ -434,7 +434,7 @@ async fn success_with_deactivating_transient_stake() { let rent = banks_client.get_rent().await.unwrap(); let stake_rent = rent.minimum_balance(std::mem::size_of::()); - let deposit_info = simple_deposit( + let deposit_info = simple_deposit_stake( &mut banks_client, &payer, &recent_blockhash, @@ -472,7 +472,7 @@ async fn success_with_deactivating_transient_stake() { assert!(error.is_none()); // fail deposit - let maybe_deposit = simple_deposit( + let maybe_deposit = simple_deposit_stake( &mut banks_client, &payer, &recent_blockhash, diff --git a/stake-pool/program/tests/withdraw.rs b/stake-pool/program/tests/withdraw.rs index 9c496e38..1af022bf 100644 --- a/stake-pool/program/tests/withdraw.rs +++ b/stake-pool/program/tests/withdraw.rs @@ -51,7 +51,7 @@ async fn setup() -> ( ) .await; - let deposit_info = simple_deposit( + let deposit_info = simple_deposit_stake( &mut banks_client, &payer, &recent_blockhash, @@ -274,7 +274,7 @@ async fn fail_with_wrong_stake_program() { let instruction = Instruction { program_id: id(), accounts, - data: instruction::StakePoolInstruction::Withdraw(tokens_to_burn) + data: instruction::StakePoolInstruction::WithdrawStake(tokens_to_burn) .try_to_vec() .unwrap(), }; @@ -361,7 +361,7 @@ async fn fail_with_wrong_token_program_id() { let wrong_token_program = Keypair::new(); let transaction = Transaction::new_signed_with_payer( - &[instruction::withdraw( + &[instruction::withdraw_stake( &id(), &stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.validator_list.pubkey(), @@ -475,6 +475,67 @@ async fn fail_with_unknown_validator() { ) .await; + let user_pool_account = Keypair::new(); + let user = 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 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.stake_deposit_authority, + withdrawer: stake_pool_accounts.stake_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 user_pool_account = user_pool_account.pubkey(); + let pool_tokens = get_token_balance(&mut banks_client, &user_pool_account).await; + + let tokens_to_burn = pool_tokens / 4; + + // Delegate tokens for burning + delegate_tokens( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &user, + &user_transfer_authority.pubkey(), + tokens_to_burn, + ) + .await; + let new_authority = Pubkey::new_unique(); let transaction_error = stake_pool_accounts .withdraw_stake( @@ -585,7 +646,7 @@ async fn fail_without_token_approval() { ) .await; - let deposit_info = simple_deposit( + let deposit_info = simple_deposit_stake( &mut banks_client, &payer, &recent_blockhash, @@ -656,7 +717,7 @@ async fn fail_with_low_delegation() { ) .await; - let deposit_info = simple_deposit( + let deposit_info = simple_deposit_stake( &mut banks_client, &payer, &recent_blockhash, @@ -796,7 +857,7 @@ async fn success_with_reserve() { let rent = context.banks_client.get_rent().await.unwrap(); let stake_rent = rent.minimum_balance(std::mem::size_of::()); - let deposit_info = simple_deposit( + let deposit_info = simple_deposit_stake( &mut context.banks_client, &context.payer, &context.last_blockhash, @@ -930,10 +991,12 @@ async fn success_with_reserve() { assert!(error.is_none()); // first and only deposit, lamports:pool 1:1 + let tokens_deposit_fee = + stake_pool_accounts.calculate_deposit_fee(deposit_info.stake_lamports + stake_rent); let tokens_withdrawal_fee = stake_pool_accounts.calculate_withdrawal_fee(deposit_info.pool_tokens); assert_eq!( - deposit_info.stake_lamports + stake_rent, + deposit_info.stake_lamports + stake_rent - tokens_deposit_fee, deposit_info.pool_tokens, ); @@ -954,8 +1017,12 @@ async fn success_with_reserve() { let stake_state = deserialize::(&reserve_stake_account.data).unwrap(); let meta = stake_state.meta().unwrap(); + // TODO: these numbers dont add up even with +tokens_deposit_fee assert_eq!( - initial_reserve_lamports + meta.rent_exempt_reserve + tokens_withdrawal_fee, + initial_reserve_lamports + + meta.rent_exempt_reserve + + tokens_withdrawal_fee + + tokens_deposit_fee, reserve_stake_account.lamports ); @@ -964,7 +1031,9 @@ async fn success_with_reserve() { get_account(&mut context.banks_client, &withdraw_destination.pubkey()).await; assert_eq!( user_stake_recipient_account.lamports, - initial_stake_lamports + deposit_info.stake_lamports + stake_rent - tokens_withdrawal_fee + initial_stake_lamports + deposit_info.stake_lamports + stake_rent + - tokens_withdrawal_fee + - tokens_deposit_fee ); } @@ -1059,7 +1128,7 @@ async fn fail_with_wrong_preferred_withdraw() { assert!(error.is_none()); // deposit into preferred, then fail - let _preferred_deposit = simple_deposit( + let _preferred_deposit = simple_deposit_stake( &mut banks_client, &payer, &recent_blockhash, @@ -1152,7 +1221,7 @@ async fn success_withdraw_from_transient() { let rent = context.banks_client.get_rent().await.unwrap(); let stake_rent = rent.minimum_balance(std::mem::size_of::()); - let deposit_info = simple_deposit( + let deposit_info = simple_deposit_stake( &mut context.banks_client, &context.payer, &context.last_blockhash,