Stake pool/liquid deposits (rebased on new master) (#2141)

* rename staking instructions

* modify DepositStake te to include manager fees and referrer, continue _stake refactor, referral fees WIP

* initialize with fees, fee application, checks

* inline functions

* temporarily substitute u8 for bool until borsh gets it's * straight

* set deposit fee

* apply deposit and referral fee to liquid deposit_sol too

* add set-deposit-fee, unify cli interface

* set-referral-fee

* full feature set for liquid deposits (?)

* add tests/set_referral_fee.rs

* fix missing serialization in process_set_referral_fee

* remove duplicated test case in tests/set_withdrawal_fee.rs

* tests WIP, numbers dont add up after non-zero deposit fee

* fix error, fix tests

* deposit_sol tests. Requires additional changes to work properly

* simplify deposit_sol tests, add referral fee tests for deposit and deposit_sol

* add `sol_deposit_authority`.

* split deposit_sol() & deposit_sol_with_authority(), cli sol_deposit --from, minor cleanup

* cli deposit-sol from argument should take keypair instead

* commands: set-sol-deposit-authority, show

* cli: pretty print stake pool struct

* chore: comments/naming

* fmt, clippy

* add args for `create-pool`

* mistake in the cli

* `system_prog` is `read_only`, require sig from `stake_deposit_auth`

* change deposit-sol-authority arg to optional acount, rename instruction::withdraw->withdraw_stake, remove unnecesary sys_prog arg for withdraw_stake

* resolve simple nits and suggestions

* cli: change default referrer to user's token receiver account instead of pool manager

* cli: remove show command, add fees to list command, rename pool -> epoch

* update tests for removed unnecessary referral fee account owner check and deposit sol

* cli changes: create ephemeral account for deposit_sol

* specify pool token type for account info name

* add check for manager fee acc in deposit_sol

* Apply suggestions from code review

Co-authored-by: Jon Cinque <jon.cinque@gmail.com>

* fix non-rebased bug

* SetDepositStakeAuthority

* refactor + tests + cli

* fmt

* clippy

* remove unnecessary comment

* ASK keyword

* unset deposit auth

* combine set fee instructions

* clippy

* applying suggestions

* apply out-of-date check only for FeeTypes that need it

* add fee + user = new tokens check

* Fix test

* Unify `SetDepositAuthority` instruction

* fmt

Co-authored-by: dhy1996 <dhy1996@live.com.sg>
Co-authored-by: Jesse Y. Cho <f8122dac91@gmail.com>
Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
This commit is contained in:
jon-chuang 2021-08-11 12:15:45 +08:00 committed by GitHub
parent 0d8a248786
commit 87d88bd078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 3079 additions and 394 deletions

View File

@ -47,7 +47,7 @@ deposit_stakes () {
for validator in $(cat $validator_list) for validator in $(cat $validator_list)
do do
stake=$(solana-keygen pubkey $keys_dir/stake_$validator.json) 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 done
} }
@ -57,7 +57,7 @@ withdraw_stakes () {
pool_amount=$3 pool_amount=$3
for validator in $(cat $validator_list) for validator in $(cat $validator_list)
do 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 done
} }

View File

@ -13,6 +13,7 @@ use {
input_parsers::{keypair_of, pubkey_of}, input_parsers::{keypair_of, pubkey_of},
input_validators::{ input_validators::{
is_amount, is_keypair, is_keypair_or_ask_keyword, is_parsable, is_pubkey, is_url, is_amount, is_keypair, is_keypair_or_ask_keyword, is_parsable, is_pubkey, is_url,
is_valid_percentage,
}, },
keypair::signer_from_path, keypair::signer_from_path,
}, },
@ -38,7 +39,7 @@ use {
find_withdraw_authority_program_address, find_withdraw_authority_program_address,
instruction::PreferredValidatorType, instruction::PreferredValidatorType,
stake_program::{self, StakeState}, stake_program::{self, StakeState},
state::{Fee, StakePool, ValidatorList}, state::{Fee, FeeType, StakePool, ValidatorList},
}, },
std::{process::exit, sync::Arc}, std::{process::exit, sync::Arc},
}; };
@ -49,6 +50,7 @@ struct Config {
manager: Box<dyn Signer>, manager: Box<dyn Signer>,
staker: Box<dyn Signer>, staker: Box<dyn Signer>,
depositor: Option<Box<dyn Signer>>, depositor: Option<Box<dyn Signer>>,
sol_depositor: Option<Box<dyn Signer>>,
token_owner: Box<dyn Signer>, token_owner: Box<dyn Signer>,
fee_payer: Box<dyn Signer>, fee_payer: Box<dyn Signer>,
dry_run: bool, dry_run: bool,
@ -153,9 +155,11 @@ fn checked_transaction_with_signers<T: Signers>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn command_create_pool( fn command_create_pool(
config: &Config, config: &Config,
deposit_authority: Option<Pubkey>, stake_deposit_authority: Option<Keypair>,
fee: Fee, fee: Fee,
withdrawal_fee: Fee, withdrawal_fee: Fee,
stake_deposit_fee: Fee,
stake_referral_fee: u8,
max_validators: u32, max_validators: u32,
stake_pool_keypair: Option<Keypair>, stake_pool_keypair: Option<Keypair>,
mint_keypair: Option<Keypair>, mint_keypair: Option<Keypair>,
@ -283,9 +287,11 @@ fn command_create_pool(
&mint_keypair.pubkey(), &mint_keypair.pubkey(),
&pool_fee_account, &pool_fee_account,
&spl_token::id(), &spl_token::id(),
deposit_authority, stake_deposit_authority.as_ref().map(|x| x.pubkey()),
fee, fee,
withdrawal_fee, withdrawal_fee,
stake_deposit_fee,
stake_referral_fee,
max_validators, max_validators,
), ),
], ],
@ -311,8 +317,15 @@ fn command_create_pool(
&validator_list, &validator_list,
config.manager.as_ref(), config.manager.as_ref(),
]; ];
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); unique_signers!(initialize_signers);
initialize_transaction.sign(&initialize_signers, recent_blockhash); initialize_transaction.sign(&initialize_signers, recent_blockhash);
} else {
unique_signers!(initialize_signers);
initialize_transaction.sign(&initialize_signers, recent_blockhash);
}
send_transaction(config, initialize_transaction)?; send_transaction(config, initialize_transaction)?;
Ok(()) Ok(())
} }
@ -567,11 +580,12 @@ fn add_associated_token_account(
account account
} }
fn command_deposit( fn command_deposit_stake(
config: &Config, config: &Config,
stake_pool_address: &Pubkey, stake_pool_address: &Pubkey,
stake: &Pubkey, stake: &Pubkey,
token_receiver: &Option<Pubkey>, pool_token_receiver_account: &Option<Pubkey>,
referrer_token_account: &Option<Pubkey>,
) -> CommandResult { ) -> CommandResult {
if !config.no_update { if !config.no_update {
command_update(config, stake_pool_address, false, false)?; command_update(config, stake_pool_address, false, false)?;
@ -613,7 +627,8 @@ fn command_deposit(
let mut total_rent_free_balances: u64 = 0; let mut total_rent_free_balances: u64 = 0;
// Create token account if not specified // Create token account if not specified
let token_receiver = token_receiver.unwrap_or(add_associated_token_account( let pool_token_receiver_account =
pool_token_receiver_account.unwrap_or(add_associated_token_account(
config, config,
&stake_pool.pool_mint, &stake_pool.pool_mint,
&config.token_owner.pubkey(), &config.token_owner.pubkey(),
@ -621,36 +636,41 @@ fn command_deposit(
&mut total_rent_free_balances, &mut total_rent_free_balances,
)); ));
let referrer_token_account = referrer_token_account.unwrap_or(pool_token_receiver_account);
let pool_withdraw_authority = let pool_withdraw_authority =
find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; 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() { let mut deposit_instructions = if let Some(stake_deposit_authority) = config.depositor.as_ref()
signers.push(deposit_authority.as_ref()); {
if deposit_authority.pubkey() != stake_pool.deposit_authority { signers.push(stake_deposit_authority.as_ref());
if stake_deposit_authority.pubkey() != stake_pool.stake_deposit_authority {
let error = format!( let error = format!(
"Invalid deposit authority specified, expected {}, received {}", "Invalid deposit authority specified, expected {}, received {}",
stake_pool.deposit_authority, stake_pool.stake_deposit_authority,
deposit_authority.pubkey() stake_deposit_authority.pubkey()
); );
return Err(error.into()); return Err(error.into());
} }
spl_stake_pool::instruction::deposit_with_authority( spl_stake_pool::instruction::deposit_stake_with_authority(
&spl_stake_pool::id(), &spl_stake_pool::id(),
stake_pool_address, stake_pool_address,
&stake_pool.validator_list, &stake_pool.validator_list,
&deposit_authority.pubkey(), &stake_deposit_authority.pubkey(),
&pool_withdraw_authority, &pool_withdraw_authority,
stake, stake,
&config.staker.pubkey(), &config.staker.pubkey(),
&validator_stake_account, &validator_stake_account,
&stake_pool.reserve_stake, &stake_pool.reserve_stake,
&token_receiver, &pool_token_receiver_account,
&stake_pool.manager_fee_account,
&referrer_token_account,
&stake_pool.pool_mint, &stake_pool.pool_mint,
&spl_token::id(), &spl_token::id(),
) )
} else { } else {
spl_stake_pool::instruction::deposit( spl_stake_pool::instruction::deposit_stake(
&spl_stake_pool::id(), &spl_stake_pool::id(),
stake_pool_address, stake_pool_address,
&stake_pool.validator_list, &stake_pool.validator_list,
@ -659,7 +679,9 @@ fn command_deposit(
&config.staker.pubkey(), &config.staker.pubkey(),
&validator_stake_account, &validator_stake_account,
&stake_pool.reserve_stake, &stake_pool.reserve_stake,
&token_receiver, &pool_token_receiver_account,
&stake_pool.manager_fee_account,
&referrer_token_account,
&stake_pool.pool_mint, &stake_pool.pool_mint,
&spl_token::id(), &spl_token::id(),
) )
@ -681,6 +703,136 @@ fn command_deposit(
Ok(()) Ok(())
} }
fn command_deposit_sol(
config: &Config,
stake_pool_address: &Pubkey,
from: &Option<Keypair>,
pool_token_receiver_account: &Option<Pubkey>,
referrer_token_account: &Option<Pubkey>,
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<Instruction> = 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 { fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult {
let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; 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)?; 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 epoch_info = config.rpc_client.get_epoch_info()?;
let pool_withdraw_authority = let pool_withdraw_authority =
find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; 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 { if config.verbose {
println!("Stake Pool Info"); 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!("Validator List: {}", stake_pool.validator_list);
println!("Manager: {}", stake_pool.manager); println!("Manager: {}", stake_pool.manager);
println!("Staker: {}", stake_pool.staker); 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!("Withdraw Authority: {}", pool_withdraw_authority);
println!("Pool Token Mint: {}", stake_pool.pool_mint); println!("Pool Token Mint: {}", stake_pool.pool_mint);
println!("Fee Account: {}", stake_pool.manager_fee_account); 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 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!( println!(
"Fee: {}/{} of epoch rewards", "Withdrawal Fee: {} of withdrawal amount",
stake_pool.fee.numerator, stake_pool.fee.denominator stake_pool.withdrawal_fee
); );
} else { } 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 { if config.verbose {
@ -1079,7 +1274,7 @@ fn command_withdraw(
stake_receiver_pubkey stake_receiver_pubkey
}); });
instructions.push(spl_stake_pool::instruction::withdraw( instructions.push(spl_stake_pool::instruction::withdraw_stake(
&spl_stake_pool::id(), &spl_stake_pool::id(),
stake_pool_address, stake_pool_address,
&stake_pool.validator_list, &stake_pool.validator_list,
@ -1179,16 +1374,22 @@ fn command_set_staker(
Ok(()) 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<Pubkey>,
for_stake_deposit: bool,
) -> CommandResult {
let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()]; let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()];
unique_signers!(signers); unique_signers!(signers);
let transaction = checked_transaction_with_signers( let transaction = checked_transaction_with_signers(
config, config,
&[spl_stake_pool::instruction::set_fee( &[spl_stake_pool::instruction::set_deposit_authority(
&spl_stake_pool::id(), &spl_stake_pool::id(),
stake_pool_address, stake_pool_address,
&config.manager.pubkey(), &config.manager.pubkey(),
new_fee, new_sol_deposit_authority.as_ref(),
for_stake_deposit,
)], )],
&signers, &signers,
)?; )?;
@ -1196,16 +1397,16 @@ fn command_set_fee(config: &Config, stake_pool_address: &Pubkey, new_fee: Fee) -
Ok(()) Ok(())
} }
fn command_set_withdrawal_fee( fn command_set_fee(
config: &Config, config: &Config,
stake_pool_address: &Pubkey, stake_pool_address: &Pubkey,
new_fee: Fee, new_fee: FeeType,
) -> CommandResult { ) -> CommandResult {
let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()]; let mut signers = vec![config.fee_payer.as_ref(), config.manager.as_ref()];
unique_signers!(signers); unique_signers!(signers);
let transaction = checked_transaction_with_signers( let transaction = checked_transaction_with_signers(
config, config,
&[spl_stake_pool::instruction::set_withdrawal_fee( &[spl_stake_pool::instruction::set_fee(
&spl_stake_pool::id(), &spl_stake_pool::id(),
stake_pool_address, stake_pool_address,
&config.manager.pubkey(), &config.manager.pubkey(),
@ -1366,6 +1567,31 @@ fn main() {
.requires("withdrawal_fee_numerator") .requires("withdrawal_fee_numerator")
.help("Withdrawal fee denominator, fee amount is numerator divided by denominator [default: 0]"), .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::<u64>)
.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::<u64>)
.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(
Arg::with_name("max_validators") Arg::with_name("max_validators")
.long("max-validators") .long("max-validators")
@ -1377,11 +1603,11 @@ fn main() {
.help("Max number of validators included in the stake pool"), .help("Max number of validators included in the stake pool"),
) )
.arg( .arg(
Arg::with_name("deposit_authority") Arg::with_name("stake_deposit_authority")
.long("deposit-authority") .long("stake-deposit-authority")
.short("a") .short("a")
.validator(is_pubkey) .validator(is_keypair_or_ask_keyword)
.value_name("DEPOSIT_AUTHORITY_ADDRESS") .value_name("STAKE_DEPOSIT_AUTHORITY_KEYPAIR")
.takes_value(true) .takes_value(true)
.help("Deposit authority required to sign all deposits into the stake pool"), .help("Deposit authority required to sign all deposits into the stake pool"),
) )
@ -1581,8 +1807,8 @@ fn main() {
.required(true) .required(true)
) )
) )
.subcommand(SubCommand::with_name("deposit") .subcommand(SubCommand::with_name("deposit-stake")
.about("Add stake account to the stake pool") .about("Deposit active stake account into the stake pool in exchange for pool tokens")
.arg( .arg(
Arg::with_name("pool") Arg::with_name("pool")
.index(1) .index(1)
@ -1611,6 +1837,61 @@ fn main() {
Defaults to the token-owner's associated pool token account. \ Defaults to the token-owner's associated pool token account. \
Creates the account if it does not exist."), 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") .subcommand(SubCommand::with_name("list")
.about("List stake accounts managed by this pool") .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."), .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") .about("Withdraw amount from the stake pool")
.arg( .arg(
Arg::with_name("pool") Arg::with_name("pool")
@ -1758,8 +2039,108 @@ fn main() {
.help("Public key for the new stake pool staker."), .help("Public key for the new stake pool staker."),
) )
) )
.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)
.validator(is_pubkey)
.value_name("POOL_ADDRESS")
.takes_value(true)
.required(true)
.help("Stake pool address."),
)
.arg(
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") .subcommand(SubCommand::with_name("set-fee")
.about("Change the fee assessed by the stake pool. Must be signed by the manager.") .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::<u64>)
.value_name("NUMERATOR")
.takes_value(true)
.required(true)
.help("Fee numerator, fee amount is numerator divided by denominator."),
)
.arg(
Arg::with_name("fee_denominator")
.index(4)
.validator(is_parsable::<u64>)
.value_name("DENOMINATOR")
.takes_value(true)
.required(true)
.help("Fee denominator, fee amount is numerator divided by denominator."),
)
)
.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(
Arg::with_name("pool") Arg::with_name("pool")
.index(1) .index(1)
@ -1770,26 +2151,16 @@ fn main() {
.help("Stake pool address."), .help("Stake pool address."),
) )
.arg( .arg(
Arg::with_name("fee_numerator") Arg::with_name("fee")
.index(2) .index(2)
.validator(is_parsable::<u64>) .validator(is_valid_percentage)
.value_name("NUMERATOR") .value_name("FEE_PERCENTAGE")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Fee numerator, fee amount is numerator divided by denominator."), .help("Fee percentage, maximum 100"),
) )
.arg( ).subcommand(SubCommand::with_name("set-sol-referral-fee")
Arg::with_name("fee_denominator") .about("Change the referral fee assessed by the stake pool for SOL deposits. Must be signed by the manager.")
.index(3)
.validator(is_parsable::<u64>)
.value_name("DENOMINATOR")
.takes_value(true)
.required(true)
.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.")
.arg( .arg(
Arg::with_name("pool") Arg::with_name("pool")
.index(1) .index(1)
@ -1800,22 +2171,13 @@ fn main() {
.help("Stake pool address."), .help("Stake pool address."),
) )
.arg( .arg(
Arg::with_name("fee_numerator") Arg::with_name("fee")
.index(2) .index(2)
.validator(is_parsable::<u64>) .validator(is_valid_percentage)
.value_name("NUMERATOR") .value_name("FEE_PERCENTAGE")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Fee numerator, fee amount is numerator divided by denominator."), .help("Fee percentage, maximum 100"),
)
.arg(
Arg::with_name("fee_denominator")
.index(3)
.validator(is_parsable::<u64>)
.value_name("DENOMINATOR")
.takes_value(true)
.required(true)
.help("Fee denominator, fee amount is numerator divided by denominator."),
) )
) )
.get_matches(); .get_matches();
@ -1847,6 +2209,16 @@ fn main() {
} else { } else {
None 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( let manager = get_signer(
&matches, &matches,
"manager", "manager",
@ -1875,6 +2247,7 @@ fn main() {
manager, manager,
staker, staker,
depositor, depositor,
sol_depositor,
token_owner, token_owner,
fee_payer, fee_payer,
dry_run, dry_run,
@ -1884,18 +2257,21 @@ fn main() {
let _ = match matches.subcommand() { let _ = match matches.subcommand() {
("create-pool", Some(arg_matches)) => { ("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 numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64);
let denominator = value_t_or_exit!(arg_matches, "fee_denominator", 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_numerator = value_t!(arg_matches, "withdrawal_fee_numerator", u64);
let w_denominator = value_t!(arg_matches, "withdrawal_fee_denominator", 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 max_validators = value_t_or_exit!(arg_matches, "max_validators", u32);
let pool_keypair = keypair_of(arg_matches, "pool_keypair"); let pool_keypair = keypair_of(arg_matches, "pool_keypair");
let mint_keypair = keypair_of(arg_matches, "mint_keypair"); let mint_keypair = keypair_of(arg_matches, "mint_keypair");
let reserve_keypair = keypair_of(arg_matches, "reserve_keypair"); let reserve_keypair = keypair_of(arg_matches, "reserve_keypair");
command_create_pool( command_create_pool(
&config, &config,
deposit_authority, stake_deposit_authority,
Fee { Fee {
denominator, denominator,
numerator, numerator,
@ -1904,6 +2280,11 @@ fn main() {
numerator: w_numerator.unwrap_or(0), numerator: w_numerator.unwrap_or(0),
denominator: w_denominator.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, max_validators,
pool_keypair, pool_keypair,
mint_keypair, mint_keypair,
@ -1956,15 +2337,32 @@ fn main() {
vote_account, vote_account,
) )
} }
("deposit", Some(arg_matches)) => { ("deposit-stake", Some(arg_matches)) => {
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
let stake_account = pubkey_of(arg_matches, "stake_account").unwrap(); let stake_account = pubkey_of(arg_matches, "stake_account").unwrap();
let token_receiver: Option<Pubkey> = pubkey_of(arg_matches, "token_receiver"); let token_receiver: Option<Pubkey> = pubkey_of(arg_matches, "token_receiver");
command_deposit( let referrer: Option<Pubkey> = pubkey_of(arg_matches, "referrer");
command_deposit_stake(
&config, &config,
&stake_pool_address, &stake_pool_address,
&stake_account, &stake_account,
&token_receiver, &token_receiver,
&referrer,
)
}
("deposit-sol", Some(arg_matches)) => {
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
let token_receiver: Option<Pubkey> = pubkey_of(arg_matches, "token_receiver");
let referrer: Option<Pubkey> = 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)) => { ("list", Some(arg_matches)) => {
@ -1977,7 +2375,7 @@ fn main() {
let force = arg_matches.is_present("force"); let force = arg_matches.is_present("force");
command_update(&config, &stake_pool_address, force, no_merge) 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 stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
let vote_account = pubkey_of(arg_matches, "vote_account"); let vote_account = pubkey_of(arg_matches, "vote_account");
let pool_account = pubkey_of(arg_matches, "pool_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(); let new_staker = pubkey_of(arg_matches, "new_staker").unwrap();
command_set_staker(&config, &stake_pool_address, &new_staker) 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)) => { ("set-fee", Some(arg_matches)) => {
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
let numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64); let numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64);
@ -2018,17 +2438,35 @@ fn main() {
denominator, denominator,
numerator, 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))
} }
("set-withdrawal-fee", Some(arg_matches)) => { "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-stake-referral-fee", Some(arg_matches)) => {
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap(); let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
let numerator = value_t_or_exit!(arg_matches, "fee_numerator", u64); let fee = value_t_or_exit!(arg_matches, "fee", u8);
let denominator = value_t_or_exit!(arg_matches, "fee_denominator", u64); if fee > 100u8 {
let new_fee = Fee { panic!("Invalid fee {}%. Fee needs to be in range [0-100]", fee);
denominator, }
numerator, command_set_fee(&config, &stake_pool_address, FeeType::StakeReferral(fee))
}; }
command_set_withdrawal_fee(&config, &stake_pool_address, new_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!(), _ => unreachable!(),
} }

View File

@ -108,6 +108,14 @@ pub enum StakePoolError {
/// Not enough lamports provided for deposit to result in one pool token /// Not enough lamports provided for deposit to result in one pool token
#[error("DepositTooSmall")] #[error("DepositTooSmall")]
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<StakePoolError> for ProgramError { impl From<StakePoolError> for ProgramError {
fn from(e: StakePoolError) -> Self { fn from(e: StakePoolError) -> Self {

View File

@ -6,7 +6,7 @@ use {
find_deposit_authority_program_address, find_stake_program_address, find_deposit_authority_program_address, find_stake_program_address,
find_transient_stake_program_address, find_withdraw_authority_program_address, find_transient_stake_program_address, find_withdraw_authority_program_address,
stake_program, stake_program,
state::{Fee, StakePool, ValidatorList}, state::{Fee, FeeType, StakePool, ValidatorList},
MAX_VALIDATORS_TO_UPDATE, MAX_VALIDATORS_TO_UPDATE,
}, },
borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
@ -28,6 +28,17 @@ pub enum PreferredValidatorType {
Withdraw, 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. /// Instructions supported by the StakePool program.
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
@ -55,6 +66,12 @@ pub enum StakePoolInstruction {
/// Fee charged per withdrawal as percentage of withdrawal /// Fee charged per withdrawal as percentage of withdrawal
#[allow(dead_code)] // but it's not #[allow(dead_code)] // but it's not
withdrawal_fee: Fee, 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 /// Maximum expected number of validators
#[allow(dead_code)] // but it's not #[allow(dead_code)] // but it's not
max_validators: u32, max_validators: u32,
@ -244,18 +261,20 @@ pub enum StakePoolInstruction {
/// ///
/// 0. `[w]` Stake pool /// 0. `[w]` Stake pool
/// 1. `[w]` Validator stake list storage account /// 1. `[w]` Validator stake list storage account
/// 2. `[]` Stake pool deposit authority /// 2. `[s]/[]` Stake pool deposit authority
/// 3. `[]` Stake pool withdraw 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) /// 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 /// 5. `[w]` Validator stake account for the stake account to be merged with
/// 6. `[w]` Reserve stake account, to withdraw rent exempt reserve /// 6. `[w]` Reserve stake account, to withdraw rent exempt reserve
/// 7. `[w]` User account to receive pool tokens /// 7. `[w]` User account to receive pool tokens
/// 8. `[w]` Pool token mint account /// 8. `[w]` Account to receive pool fee tokens
/// 9. '[]' Sysvar clock account /// 9. `[w]` Account to receive a portion of pool fee tokens as referral fees
/// 10. '[]' Sysvar stake history account /// 10. `[w]` Pool token mint account
/// 11. `[]` Pool token program id, /// 11. '[]' Sysvar clock account
/// 12. `[]` Stake program id, /// 12. '[]' Sysvar stake history account
Deposit, /// 13. `[]` Pool token program id,
/// 14. `[]` Stake program id,
DepositStake,
/// Withdraw the token from the pool at the current ratio. /// Withdraw the token from the pool at the current ratio.
/// ///
@ -281,7 +300,7 @@ pub enum StakePoolInstruction {
/// 11. `[]` Pool token program id /// 11. `[]` Pool token program id
/// 12. `[]` Stake program id, /// 12. `[]` Stake program id,
/// userdata: amount of pool tokens to withdraw /// userdata: amount of pool tokens to withdraw
Withdraw(u64), WithdrawStake(u64),
/// (Manager only) Update manager /// (Manager only) Update manager
/// ///
@ -297,9 +316,9 @@ pub enum StakePoolInstruction {
/// 1. `[s]` Manager /// 1. `[s]` Manager
/// 2. `[]` Sysvar clock /// 2. `[]` Sysvar clock
SetFee { 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 #[allow(dead_code)] // but it's not
fee: Fee, fee: FeeType,
}, },
/// (Manager or staker only) Update staker /// (Manager or staker only) Update staker
@ -309,16 +328,29 @@ pub enum StakePoolInstruction {
/// 2. '[]` New staker pubkey /// 2. '[]` New staker pubkey
SetStaker, 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 /// 0. `[w]` StakePool
/// 1. `[s]` Manager /// 1. `[s]` Manager
/// 2. `[]` Sysvar clock /// 2. '[]` New sol_deposit_authority pubkey or none
SetWithdrawalFee { SetDepositAuthority(DepositType),
/// Fee assessed as percentage of perceived rewards
#[allow(dead_code)] // but it's not
fee: Fee,
},
} }
/// Creates an 'initialize' instruction. /// Creates an 'initialize' instruction.
@ -335,11 +367,15 @@ pub fn initialize(
deposit_authority: Option<Pubkey>, deposit_authority: Option<Pubkey>,
fee: Fee, fee: Fee,
withdrawal_fee: Fee, withdrawal_fee: Fee,
deposit_fee: Fee,
referral_fee: u8,
max_validators: u32, max_validators: u32,
) -> Instruction { ) -> Instruction {
let init_data = StakePoolInstruction::Initialize { let init_data = StakePoolInstruction::Initialize {
fee, fee,
withdrawal_fee, withdrawal_fee,
deposit_fee,
referral_fee,
max_validators, max_validators,
}; };
let data = init_data.try_to_vec().unwrap(); 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 /// Creates instructions required to deposit into a stake pool, given a stake
/// account owned by the user. /// account owned by the user.
pub fn deposit( pub fn deposit_stake(
program_id: &Pubkey, program_id: &Pubkey,
stake_pool: &Pubkey, stake_pool: &Pubkey,
validator_list_storage: &Pubkey, validator_list_storage: &Pubkey,
@ -836,6 +872,8 @@ pub fn deposit(
validator_stake_account: &Pubkey, validator_stake_account: &Pubkey,
reserve_stake_account: &Pubkey, reserve_stake_account: &Pubkey,
pool_tokens_to: &Pubkey, pool_tokens_to: &Pubkey,
manager_fee_account: &Pubkey,
referrer_pool_tokens_account: &Pubkey,
pool_mint: &Pubkey, pool_mint: &Pubkey,
token_program_id: &Pubkey, token_program_id: &Pubkey,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
@ -850,6 +888,8 @@ pub fn deposit(
AccountMeta::new(*validator_stake_account, false), AccountMeta::new(*validator_stake_account, false),
AccountMeta::new(*reserve_stake_account, false), AccountMeta::new(*reserve_stake_account, false),
AccountMeta::new(*pool_tokens_to, 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(*pool_mint, false),
AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false),
@ -872,7 +912,7 @@ pub fn deposit(
Instruction { Instruction {
program_id: *program_id, program_id: *program_id,
accounts, 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 /// 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 /// account owned by the user. The difference with `deposit()` is that a deposit
/// authority must sign this instruction, which is required for private pools. /// 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, program_id: &Pubkey,
stake_pool: &Pubkey, stake_pool: &Pubkey,
validator_list_storage: &Pubkey, validator_list_storage: &Pubkey,
@ -891,6 +931,8 @@ pub fn deposit_with_authority(
validator_stake_account: &Pubkey, validator_stake_account: &Pubkey,
reserve_stake_account: &Pubkey, reserve_stake_account: &Pubkey,
pool_tokens_to: &Pubkey, pool_tokens_to: &Pubkey,
manager_fee_account: &Pubkey,
referrer_pool_tokens_account: &Pubkey,
pool_mint: &Pubkey, pool_mint: &Pubkey,
token_program_id: &Pubkey, token_program_id: &Pubkey,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
@ -903,6 +945,8 @@ pub fn deposit_with_authority(
AccountMeta::new(*validator_stake_account, false), AccountMeta::new(*validator_stake_account, false),
AccountMeta::new(*reserve_stake_account, false), AccountMeta::new(*reserve_stake_account, false),
AccountMeta::new(*pool_tokens_to, 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(*pool_mint, false),
AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false), AccountMeta::new_readonly(sysvar::stake_history::id(), false),
@ -925,13 +969,91 @@ pub fn deposit_with_authority(
Instruction { Instruction {
program_id: *program_id, program_id: *program_id,
accounts, accounts,
data: StakePoolInstruction::Deposit.try_to_vec().unwrap(), data: StakePoolInstruction::DepositStake.try_to_vec().unwrap(),
}, },
] ]
} }
/// Creates a 'withdraw' instruction. /// Creates instructions required to deposit SOL directly into a stake pool.
pub fn withdraw( 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<Instruction> {
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<Instruction> {
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, program_id: &Pubkey,
stake_pool: &Pubkey, stake_pool: &Pubkey,
validator_list_storage: &Pubkey, validator_list_storage: &Pubkey,
@ -964,7 +1086,9 @@ pub fn withdraw(
Instruction { Instruction {
program_id: *program_id, program_id: *program_id,
accounts, 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, program_id: &Pubkey,
stake_pool: &Pubkey, stake_pool: &Pubkey,
manager: &Pubkey, manager: &Pubkey,
fee: Fee, fee: FeeType,
) -> Instruction { ) -> Instruction {
let accounts = vec![ let accounts = vec![
AccountMeta::new(*stake_pool, false), 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( pub fn set_withdrawal_fee(
program_id: &Pubkey, program_id: &Pubkey,
stake_pool: &Pubkey, stake_pool: &Pubkey,
manager: &Pubkey, manager: &Pubkey,
fee: Fee, fee: Fee,
) -> Instruction { ) -> Instruction {
let accounts = vec![ set_fee(program_id, stake_pool, manager, FeeType::Withdrawal(fee))
AccountMeta::new(*stake_pool, false), }
AccountMeta::new_readonly(*manager, true),
AccountMeta::new_readonly(sysvar::clock::id(), false), /// Creates a 'set fee' instruction for setting SOL deposit fee
]; pub fn set_sol_deposit_fee(
Instruction { program_id: &Pubkey,
program_id: *program_id, stake_pool: &Pubkey,
accounts, manager: &Pubkey,
data: StakePoolInstruction::SetWithdrawalFee { fee } fee: Fee,
.try_to_vec() ) -> Instruction {
.unwrap(), 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. /// Creates a 'set staker' instruction.
@ -1047,3 +1210,79 @@ pub fn set_staker(
data: StakePoolInstruction::SetStaker.try_to_vec().unwrap(), 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(),
}
}

View File

@ -51,6 +51,7 @@ pub const WITHDRAWAL_BASELINE_FEE: Fee = Fee {
/// Get the stake amount under consideration when calculating pool token /// Get the stake amount under consideration when calculating pool token
/// conversions /// conversions
#[inline]
pub fn minimum_stake_lamports(meta: &Meta) -> u64 { pub fn minimum_stake_lamports(meta: &Meta) -> u64 {
meta.rent_exempt_reserve meta.rent_exempt_reserve
.saturating_add(MINIMUM_ACTIVE_STAKE) .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 /// Get the stake amount under consideration when calculating pool token
/// conversions /// conversions
#[inline]
pub fn minimum_reserve_lamports(meta: &Meta) -> u64 { pub fn minimum_reserve_lamports(meta: &Meta) -> u64 {
meta.rent_exempt_reserve.saturating_add(1) meta.rent_exempt_reserve.saturating_add(1)
} }

View File

@ -1,5 +1,6 @@
//! Program state processor //! Program state processor
use crate::instruction::DepositType;
use { use {
crate::{ crate::{
error::StakePoolError, error::StakePoolError,
@ -7,11 +8,10 @@ use {
instruction::{PreferredValidatorType, StakePoolInstruction}, instruction::{PreferredValidatorType, StakePoolInstruction},
minimum_reserve_lamports, minimum_stake_lamports, stake_program, minimum_reserve_lamports, minimum_stake_lamports, stake_program,
state::{ state::{
AccountType, Fee, StakePool, StakeStatus, ValidatorList, ValidatorListHeader, AccountType, Fee, FeeType, StakePool, StakeStatus, ValidatorList, ValidatorListHeader,
ValidatorStakeInfo, ValidatorStakeInfo,
}, },
AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MAX_WITHDRAWAL_FEE_INCREASE, MINIMUM_ACTIVE_STAKE, AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MINIMUM_ACTIVE_STAKE, TRANSIENT_STAKE_SEED,
TRANSIENT_STAKE_SEED, WITHDRAWAL_BASELINE_FEE,
}, },
borsh::{BorshDeserialize, BorshSerialize}, borsh::{BorshDeserialize, BorshSerialize},
num_traits::FromPrimitive, num_traits::FromPrimitive,
@ -468,12 +468,24 @@ impl Processor {
invoke(&ix, &[source, destination, authority, token_program]) 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. /// Processes `Initialize` instruction.
fn process_initialize( fn process_initialize(
program_id: &Pubkey, program_id: &Pubkey,
accounts: &[AccountInfo], accounts: &[AccountInfo],
fee: Fee, fee: Fee,
withdrawal_fee: Fee, withdrawal_fee: Fee,
stake_deposit_fee: Fee,
stake_referral_fee: u8,
max_validators: u32, max_validators: u32,
) -> ProgramResult { ) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();
@ -538,7 +550,11 @@ impl Processor {
} }
// Numerator should be smaller than or equal to denominator (fee <= 1) // 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()); return Err(StakePoolError::FeeTooHigh.into());
} }
@ -556,11 +572,11 @@ impl Processor {
return Err(StakePoolError::WrongAccountMint.into()); return Err(StakePoolError::WrongAccountMint.into());
} }
let deposit_authority = match next_account_info(account_info_iter) { let stake_deposit_authority = match next_account_info(account_info_iter) {
Ok(deposit_authority_info) => *deposit_authority_info.key, Ok(stake_deposit_authority_info) => *stake_deposit_authority_info.key,
Err(_) => find_deposit_authority_program_address(program_id, stake_pool_info.key).0, 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); crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key);
let pool_mint = Mint::unpack_from_slice(&pool_mint_info.data.borrow())?; 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"); msg!("Reserve stake account not in intialized state");
return Err(StakePoolError::WrongStakeState.into()); 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())?; validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
@ -627,8 +639,8 @@ impl Processor {
stake_pool.manager = *manager_info.key; stake_pool.manager = *manager_info.key;
stake_pool.staker = *staker_info.key; stake_pool.staker = *staker_info.key;
stake_pool.reserve_stake = *reserve_stake_info.key; stake_pool.reserve_stake = *reserve_stake_info.key;
stake_pool.deposit_authority = deposit_authority; stake_pool.stake_deposit_authority = stake_deposit_authority;
stake_pool.withdraw_bump_seed = withdraw_bump_seed; stake_pool.stake_withdraw_bump_seed = stake_withdraw_bump_seed;
stake_pool.validator_list = *validator_list_info.key; stake_pool.validator_list = *validator_list_info.key;
stake_pool.pool_mint = *pool_mint_info.key; stake_pool.pool_mint = *pool_mint_info.key;
stake_pool.manager_fee_account = *manager_fee_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.next_epoch_fee = None;
stake_pool.preferred_deposit_validator_vote_address = None; stake_pool.preferred_deposit_validator_vote_address = None;
stake_pool.preferred_withdraw_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.withdrawal_fee = withdrawal_fee;
stake_pool.next_withdrawal_fee = None; stake_pool.next_withdrawal_fee = None;
stake_pool.stake_referral_fee = stake_referral_fee;
stake_pool.sol_deposit_authority = None;
stake_pool stake_pool
.serialize(&mut *stake_pool_info.data.borrow_mut()) .serialize(&mut *stake_pool_info.data.borrow_mut())
@ -798,7 +812,7 @@ impl Processor {
)?; )?;
if meta.lockup != stake_program::Lockup::default() { 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()); return Err(StakePoolError::WrongStakeState.into());
} }
@ -813,20 +827,13 @@ impl Processor {
// Check amount of lamports // Check amount of lamports
let stake_lamports = **stake_account_info.lamports.borrow(); let stake_lamports = **stake_account_info.lamports.borrow();
let minimum_lamport_amount = minimum_stake_lamports(&meta); 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!( msg!(
"Error: attempting to add stake with {} lamports, must have {} lamports", "Error: attempting to add (stake: {}, delegation: {}), below minimum",
stake_lamports, 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, stake.delegation.stake,
MINIMUM_ACTIVE_STAKE
); );
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into()); return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
} }
@ -970,7 +977,7 @@ impl Processor {
stake_account_info.clone(), stake_account_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
new_stake_authority_info.key, new_stake_authority_info.key,
clock_info.clone(), clock_info.clone(),
stake_program_info.clone(), stake_program_info.clone(),
@ -1097,7 +1104,7 @@ impl Processor {
validator_stake_account_info.clone(), validator_stake_account_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
lamports, lamports,
transient_stake_account_info.clone(), transient_stake_account_info.clone(),
)?; )?;
@ -1109,7 +1116,7 @@ impl Processor {
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
stake_pool_info.key, stake_pool_info.key,
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
)?; )?;
validator_stake_info.active_stake_lamports = validator_stake_info validator_stake_info.active_stake_lamports = validator_stake_info
@ -1249,7 +1256,7 @@ impl Processor {
reserve_stake_account_info.clone(), reserve_stake_account_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
total_lamports, total_lamports,
transient_stake_account_info.clone(), transient_stake_account_info.clone(),
)?; )?;
@ -1264,7 +1271,7 @@ impl Processor {
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
stake_pool_info.key, stake_pool_info.key,
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
)?; )?;
validator_stake_info.transient_stake_lamports = total_lamports; validator_stake_info.transient_stake_lamports = total_lamports;
@ -1427,7 +1434,7 @@ impl Processor {
transient_stake_info.clone(), transient_stake_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
reserve_stake_info.clone(), reserve_stake_info.clone(),
clock_info.clone(), clock_info.clone(),
stake_history_info.clone(), stake_history_info.clone(),
@ -1453,7 +1460,7 @@ impl Processor {
transient_stake_info.clone(), transient_stake_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
reserve_stake_info.clone(), reserve_stake_info.clone(),
clock_info.clone(), clock_info.clone(),
stake_history_info.clone(), stake_history_info.clone(),
@ -1479,7 +1486,7 @@ impl Processor {
transient_stake_info.clone(), transient_stake_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
validator_stake_info.clone(), validator_stake_info.clone(),
clock_info.clone(), clock_info.clone(),
stake_history_info.clone(), stake_history_info.clone(),
@ -1494,7 +1501,7 @@ impl Processor {
validator_stake_info.clone(), validator_stake_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
reserve_stake_info.clone(), reserve_stake_info.clone(),
clock_info.clone(), clock_info.clone(),
stake_history_info.clone(), stake_history_info.clone(),
@ -1623,7 +1630,7 @@ impl Processor {
let reward_lamports = total_stake_lamports.saturating_sub(previous_lamports); let reward_lamports = total_stake_lamports.saturating_sub(previous_lamports);
let fee = stake_pool let fee = stake_pool
.calc_fee_amount(reward_lamports) .calc_epoch_fee_amount(reward_lamports)
.ok_or(StakePoolError::CalculationFailure)?; .ok_or(StakePoolError::CalculationFailure)?;
if fee > 0 { if fee > 0 {
@ -1634,7 +1641,7 @@ impl Processor {
manager_fee_info.clone(), manager_fee_info.clone(),
withdraw_info.clone(), withdraw_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
fee, fee,
)?; )?;
@ -1718,17 +1725,19 @@ impl Processor {
Ok(()) Ok(())
} }
/// Processes [Deposit](enum.Instruction.html). /// Processes [DepositStake](enum.Instruction.html).
fn process_deposit(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { fn process_deposit_stake(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();
let stake_pool_info = next_account_info(account_info_iter)?; let stake_pool_info = next_account_info(account_info_iter)?;
let validator_list_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 withdraw_authority_info = next_account_info(account_info_iter)?;
let stake_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 validator_stake_account_info = next_account_info(account_info_iter)?;
let reserve_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 pool_mint_info = next_account_info(account_info_iter)?;
let clock_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 clock = &Clock::from_account_info(clock_info)?;
@ -1753,7 +1762,7 @@ impl Processor {
program_id, program_id,
stake_pool_info.key, 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_mint(pool_mint_info)?;
stake_pool.check_validator_list(validator_list_info)?; stake_pool.check_validator_list(validator_list_info)?;
stake_pool.check_reserve_stake(reserve_stake_account_info)?; stake_pool.check_reserve_stake(reserve_stake_account_info)?;
@ -1762,6 +1771,10 @@ impl Processor {
return Err(ProgramError::IncorrectProgramId); 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 { if stake_pool.last_update_epoch < clock.epoch {
return Err(StakePoolError::StakeListAndPoolOutOfDate.into()); return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
} }
@ -1808,13 +1821,13 @@ impl Processor {
msg!("Stake pre merge {}", validator_stake.delegation.stake); 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); 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( Self::stake_authorize_signed(
stake_pool_info.key, stake_pool_info.key,
stake_info.clone(), stake_info.clone(),
deposit_authority_info.clone(), stake_deposit_authority_info.clone(),
AUTHORITY_DEPOSIT, AUTHORITY_DEPOSIT,
deposit_bump_seed, deposit_bump_seed,
withdraw_authority_info.key, withdraw_authority_info.key,
@ -1824,7 +1837,7 @@ impl Processor {
} else { } else {
Self::stake_authorize( Self::stake_authorize(
stake_info.clone(), stake_info.clone(),
deposit_authority_info.clone(), stake_deposit_authority_info.clone(),
withdraw_authority_info.key, withdraw_authority_info.key,
clock_info.clone(), clock_info.clone(),
stake_program_info.clone(), stake_program_info.clone(),
@ -1836,7 +1849,7 @@ impl Processor {
stake_info.clone(), stake_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
validator_stake_account_info.clone(), validator_stake_account_info.clone(),
clock_info.clone(), clock_info.clone(),
stake_history_info.clone(), stake_history_info.clone(),
@ -1854,24 +1867,71 @@ impl Processor {
.stake .stake
.checked_sub(validator_stake.delegation.stake) .checked_sub(validator_stake.delegation.stake)
.ok_or(StakePoolError::CalculationFailure)?; .ok_or(StakePoolError::CalculationFailure)?;
let new_pool_tokens = stake_pool let new_pool_tokens = stake_pool
.calc_pool_tokens_for_deposit(all_deposit_lamports) .calc_pool_tokens_for_deposit(all_deposit_lamports)
.ok_or(StakePoolError::CalculationFailure)?; .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 { if new_pool_tokens == 0 {
return Err(StakePoolError::DepositTooSmall.into()); return Err(StakePoolError::DepositTooSmall.into());
} }
if pool_tokens_user > 0 {
Self::token_mint_to( Self::token_mint_to(
stake_pool_info.key, stake_pool_info.key,
token_program_info.clone(), token_program_info.clone(),
pool_mint_info.clone(), pool_mint_info.clone(),
dest_user_info.clone(), dest_user_pool_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
new_pool_tokens, 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 // withdraw additional lamports to the reserve
let additional_lamports = all_deposit_lamports let additional_lamports = all_deposit_lamports
@ -1883,7 +1943,7 @@ impl Processor {
validator_stake_account_info.clone(), validator_stake_account_info.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
reserve_stake_account_info.clone(), reserve_stake_account_info.clone(),
clock_info.clone(), clock_info.clone(),
stake_history_info.clone(), stake_history_info.clone(),
@ -1911,8 +1971,146 @@ impl Processor {
Ok(()) Ok(())
} }
/// Processes [Withdraw](enum.Instruction.html). /// Processes [DepositStake](enum.Instruction.html).
fn process_withdraw( 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::<StakePool>(&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, program_id: &Pubkey,
accounts: &[AccountInfo], accounts: &[AccountInfo],
pool_tokens: u64, pool_tokens: u64,
@ -1925,7 +2123,7 @@ impl Processor {
let stake_split_to = next_account_info(account_info_iter)?; let stake_split_to = next_account_info(account_info_iter)?;
let user_stake_authority_info = 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 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 manager_fee_info = next_account_info(account_info_iter)?;
let pool_mint_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_info = next_account_info(account_info_iter)?;
@ -1967,7 +2165,7 @@ impl Processor {
return Err(StakePoolError::InvalidState.into()); 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 0
} else { } else {
stake_pool stake_pool
@ -2079,7 +2277,7 @@ impl Processor {
Self::token_burn( Self::token_burn(
token_program_info.clone(), token_program_info.clone(),
burn_from_info.clone(), burn_from_pool_info.clone(),
pool_mint_info.clone(), pool_mint_info.clone(),
user_transfer_authority_info.clone(), user_transfer_authority_info.clone(),
pool_tokens_burnt, pool_tokens_burnt,
@ -2090,7 +2288,7 @@ impl Processor {
stake_split_from.clone(), stake_split_from.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
withdraw_lamports, withdraw_lamports,
stake_split_to.clone(), stake_split_to.clone(),
)?; )?;
@ -2100,7 +2298,7 @@ impl Processor {
stake_split_to.clone(), stake_split_to.clone(),
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.stake_withdraw_bump_seed,
user_stake_authority_info.key, user_stake_authority_info.key,
clock_info.clone(), clock_info.clone(),
stake_program_info.clone(), stake_program_info.clone(),
@ -2109,7 +2307,7 @@ impl Processor {
if pool_tokens_fee > 0 { if pool_tokens_fee > 0 {
Self::token_transfer( Self::token_transfer(
token_program_info.clone(), token_program_info.clone(),
burn_from_info.clone(), burn_from_pool_info.clone(),
manager_fee_info.clone(), manager_fee_info.clone(),
user_transfer_authority_info.clone(), user_transfer_authority_info.clone(),
pool_tokens_fee, pool_tokens_fee,
@ -2175,7 +2373,11 @@ impl Processor {
} }
/// Processes [SetFee](enum.Instruction.html). /// 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 account_info_iter = &mut accounts.iter();
let stake_pool_info = next_account_info(account_info_iter)?; let stake_pool_info = next_account_info(account_info_iter)?;
let manager_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)?; 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()); return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
} }
// Numerator should be smaller than or equal to denominator (fee <= 1) fee.check_too_high()?;
if fee.numerator > fee.denominator { fee.check_withdrawal(&stake_pool.withdrawal_fee)?;
msg!(
"Fee greater than 100%, numerator {}, denominator {}",
fee.numerator,
fee.denominator
);
return Err(StakePoolError::FeeTooHigh.into());
}
stake_pool.next_epoch_fee = Some(fee); stake_pool.update_fee(&fee);
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?; stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
Ok(()) Ok(())
} }
@ -2232,76 +2427,34 @@ impl Processor {
Ok(()) Ok(())
} }
/// Processes [SetWithdrawalFee](enum.Instruction.html). /// Processes [SetStakeDepositAuthority/SetSolDepositAuthority](enum.Instruction.html).
fn process_set_withdrawal_fee( fn process_set_deposit_authority(
program_id: &Pubkey, program_id: &Pubkey,
accounts: &[AccountInfo], accounts: &[AccountInfo],
fee: Fee, deposit_type: DepositType,
) -> ProgramResult { ) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();
let stake_pool_info = next_account_info(account_info_iter)?; let stake_pool_info = next_account_info(account_info_iter)?;
let manager_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)?; check_account_owner(stake_pool_info, program_id)?;
let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?; let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
if !stake_pool.is_valid() { if !stake_pool.is_valid() {
return Err(StakePoolError::InvalidState.into()); return Err(StakePoolError::InvalidState.into());
} }
stake_pool.check_manager(manager_info)?; stake_pool.check_manager(manager_info)?;
match deposit_type {
if stake_pool.last_update_epoch < clock.epoch { DepositType::Stake => {
return Err(StakePoolError::StakeListAndPoolOutOfDate.into()); stake_pool.stake_deposit_authority = new_sol_deposit_authority.unwrap_or(
} find_deposit_authority_program_address(program_id, stake_pool_info.key).0,
// 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());
} }
DepositType::Sol => stake_pool.sol_deposit_authority = new_sol_deposit_authority,
// 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())?; stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
Ok(()) Ok(())
} }
@ -2313,10 +2466,20 @@ impl Processor {
StakePoolInstruction::Initialize { StakePoolInstruction::Initialize {
fee, fee,
withdrawal_fee, withdrawal_fee,
deposit_fee,
referral_fee,
max_validators, max_validators,
} => { } => {
msg!("Instruction: Initialize stake pool"); 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 => { StakePoolInstruction::CreateValidatorStakeAccount => {
msg!("Instruction: CreateValidatorStakeAccount"); msg!("Instruction: CreateValidatorStakeAccount");
@ -2370,13 +2533,13 @@ impl Processor {
msg!("Instruction: CleanupRemovedValidatorEntries"); msg!("Instruction: CleanupRemovedValidatorEntries");
Self::process_cleanup_removed_validator_entries(program_id, accounts) Self::process_cleanup_removed_validator_entries(program_id, accounts)
} }
StakePoolInstruction::Deposit => { StakePoolInstruction::DepositStake => {
msg!("Instruction: Deposit"); msg!("Instruction: DepositStake");
Self::process_deposit(program_id, accounts) Self::process_deposit_stake(program_id, accounts)
} }
StakePoolInstruction::Withdraw(amount) => { StakePoolInstruction::WithdrawStake(amount) => {
msg!("Instruction: Withdraw"); msg!("Instruction: WithdrawStake");
Self::process_withdraw(program_id, accounts, amount) Self::process_withdraw_stake(program_id, accounts, amount)
} }
StakePoolInstruction::SetManager => { StakePoolInstruction::SetManager => {
msg!("Instruction: SetManager"); msg!("Instruction: SetManager");
@ -2390,9 +2553,13 @@ impl Processor {
msg!("Instruction: SetStaker"); msg!("Instruction: SetStaker");
Self::process_set_staker(program_id, accounts) Self::process_set_staker(program_id, accounts)
} }
StakePoolInstruction::SetWithdrawalFee { fee } => { StakePoolInstruction::DepositSol(lamports) => {
msg!("Instruction: SetWithdrawalFee"); msg!("Instruction: DepositSol");
Self::process_set_withdrawal_fee(program_id, accounts, fee) 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::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::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::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"),
} }
} }
} }

View File

@ -1,7 +1,10 @@
//! State transition types //! State transition types
use { 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}, borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
num_derive::FromPrimitive, num_derive::FromPrimitive,
num_traits::FromPrimitive, num_traits::FromPrimitive,
@ -16,6 +19,7 @@ use {
}, },
spl_math::checked_ceil_div::CheckedCeilDiv, spl_math::checked_ceil_div::CheckedCeilDiv,
std::convert::TryFrom, std::convert::TryFrom,
std::{fmt, matches},
}; };
/// Enum representing the account type managed by the program /// Enum representing the account type managed by the program
@ -49,7 +53,7 @@ pub struct StakePool {
/// distribution /// distribution
pub staker: Pubkey, pub staker: Pubkey,
/// Deposit authority /// Stake deposit authority
/// ///
/// If a depositor pubkey is specified on initialization, then deposits must be /// If a depositor pubkey is specified on initialization, then deposits must be
/// signed by this authority. If no deposit authority is specified, /// 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"], /// &[&stake_pool_address.to_bytes()[..32], b"deposit"],
/// program_id, /// 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"])` /// for `create_program_address(&[state::StakePool account, "withdrawal"])`
pub withdraw_bump_seed: u8, pub stake_withdraw_bump_seed: u8,
/// Validator stake list storage account /// Validator stake list storage account
pub validator_list: Pubkey, pub validator_list: Pubkey,
@ -105,8 +109,8 @@ pub struct StakePool {
/// Preferred withdraw validator vote account pubkey /// Preferred withdraw validator vote account pubkey
pub preferred_withdraw_validator_vote_address: Option<Pubkey>, pub preferred_withdraw_validator_vote_address: Option<Pubkey>,
/// Fee assessed on deposits /// Fee assessed on stake deposits
pub deposit_fee: Fee, pub stake_deposit_fee: Fee,
/// Fee assessed on withdrawals /// Fee assessed on withdrawals
pub withdrawal_fee: Fee, pub withdrawal_fee: Fee,
@ -114,14 +118,28 @@ pub struct StakePool {
/// Future withdrawal fee, to be set for the following epoch /// Future withdrawal fee, to be set for the following epoch
pub next_withdrawal_fee: Option<Fee>, pub next_withdrawal_fee: Option<Fee>,
/// 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. /// Expressed as a percentage (0 - 100) of deposit fees.
/// i.e. `deposit_fee`% is collected as deposit fees for every deposit /// i.e. `stake_deposit_fee`% of stake deposited is collected as deposit fees for every deposit
/// and `referral_fee`% of the collected deposit fees is paid out to the referrer /// and `stake_referral_fee`% of the collected stake deposit fees is paid out to the referrer
pub referral_fee: u8, pub stake_referral_fee: u8,
/// Toggles whether the `DepositSol` instruction requires a signature from
/// the `deposit_authority`
pub sol_deposit_authority: Option<Pubkey>,
/// 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 { impl StakePool {
/// calculate the pool tokens that should be minted for a deposit of `stake_lamports` /// 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<u64> { pub fn calc_pool_tokens_for_deposit(&self, stake_lamports: u64) -> Option<u64> {
if self.total_stake_lamports == 0 || self.pool_token_supply == 0 { if self.total_stake_lamports == 0 || self.pool_token_supply == 0 {
return Some(stake_lamports); return Some(stake_lamports);
@ -135,6 +153,7 @@ impl StakePool {
} }
/// calculate lamports amount on withdrawal /// calculate lamports amount on withdrawal
#[inline]
pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> { pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> {
// `checked_ceil_div` returns `None` for a 0 quotient result, but in this // `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 // 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 /// calculate pool tokens to be deducted as withdrawal fees
#[inline]
pub fn calc_pool_tokens_withdrawal_fee(&self, pool_tokens: u64) -> Option<u64> { pub fn calc_pool_tokens_withdrawal_fee(&self, pool_tokens: u64) -> Option<u64> {
u64::try_from(self.withdrawal_fee.apply(pool_tokens)?).ok() 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> {
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> {
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> {
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> {
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 /// Calculate the fee in pool tokens that goes to the manager
/// ///
/// This function assumes that `reward_lamports` has not already been added /// This function assumes that `reward_lamports` has not already been added
/// to the stake pool's `total_stake_lamports` /// to the stake pool's `total_stake_lamports`
pub fn calc_fee_amount(&self, reward_lamports: u64) -> Option<u64> { #[inline]
pub fn calc_epoch_fee_amount(&self, reward_lamports: u64) -> Option<u64> {
if reward_lamports == 0 { if reward_lamports == 0 {
return Some(0); return Some(0);
} }
@ -178,7 +233,7 @@ impl StakePool {
} }
/// Checks that the withdraw or deposit authority is valid /// Checks that the withdraw or deposit authority is valid
fn check_authority( fn check_program_derived_authority(
authority_address: &Pubkey, authority_address: &Pubkey,
program_id: &Pubkey, program_id: &Pubkey,
stake_pool_address: &Pubkey, stake_pool_address: &Pubkey,
@ -207,33 +262,55 @@ impl StakePool {
} }
/// Checks that the withdraw authority is valid /// Checks that the withdraw authority is valid
#[inline]
pub(crate) fn check_authority_withdraw( pub(crate) fn check_authority_withdraw(
&self, &self,
withdraw_authority: &Pubkey, withdraw_authority: &Pubkey,
program_id: &Pubkey, program_id: &Pubkey,
stake_pool_address: &Pubkey, stake_pool_address: &Pubkey,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
Self::check_authority( Self::check_program_derived_authority(
withdraw_authority, withdraw_authority,
program_id, program_id,
stake_pool_address, stake_pool_address,
crate::AUTHORITY_WITHDRAW, crate::AUTHORITY_WITHDRAW,
self.withdraw_bump_seed, self.stake_withdraw_bump_seed,
) )
} }
/// Checks that the deposit authority is valid /// Checks that the deposit authority is valid
pub(crate) fn check_deposit_authority( #[inline]
pub(crate) fn check_stake_deposit_authority(
&self, &self,
deposit_authority: &Pubkey, stake_deposit_authority: &Pubkey,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
if self.deposit_authority == *deposit_authority { if self.stake_deposit_authority == *stake_deposit_authority {
Ok(()) Ok(())
} else { } 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 /// Check staker validity and signature
#[inline]
pub(crate) fn check_mint(&self, mint_info: &AccountInfo) -> Result<(), ProgramError> { pub(crate) fn check_mint(&self, mint_info: &AccountInfo) -> Result<(), ProgramError> {
if *mint_info.key != self.pool_mint { if *mint_info.key != self.pool_mint {
Err(StakePoolError::WrongPoolMint.into()) Err(StakePoolError::WrongPoolMint.into())
@ -319,6 +396,18 @@ impl StakePool {
pub fn is_uninitialized(&self) -> bool { pub fn is_uninitialized(&self) -> bool {
self.account_type == AccountType::Uninitialized 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. /// 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)] #[cfg(test)]
mod test { mod test {
use { use {
@ -779,7 +958,7 @@ mod test {
..StakePool::default() ..StakePool::default()
}; };
let reward_lamports = 10 * LAMPORTS_PER_SOL; 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.total_stake_lamports += reward_lamports;
stake_pool.pool_token_supply += pool_token_fee; stake_pool.pool_token_supply += pool_token_fee;
@ -815,7 +994,7 @@ mod test {
..StakePool::default() ..StakePool::default()
}; };
let rewards = 10; 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); assert_eq!(fee, rewards);
} }
@ -832,7 +1011,7 @@ mod test {
fee, fee,
..StakePool::default() ..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.total_stake_lamports += reward_lamports;
stake_pool.pool_token_supply += pool_token_fee; stake_pool.pool_token_supply += pool_token_fee;

View File

@ -41,7 +41,7 @@ async fn setup() -> (
) )
.await; .await;
let deposit_info = simple_deposit( let deposit_info = simple_deposit_stake(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,

View File

@ -212,7 +212,9 @@ async fn success() {
// Check minted tokens // Check minted tokens
let user_token_balance = let user_token_balance =
get_token_balance(&mut context.banks_client, &pool_token_account).await; 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 // Check balances in validator stake account list storage
let validator_list = get_account( let validator_list = get_account(
@ -272,12 +274,14 @@ async fn fail_with_wrong_stake_program_id() {
let accounts = vec![ let accounts = vec![
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
AccountMeta::new(stake_pool_accounts.validator_list.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_readonly(stake_pool_accounts.withdraw_authority, false),
AccountMeta::new(deposit_stake, false), AccountMeta::new(deposit_stake, false),
AccountMeta::new(validator_stake_account.stake_account, false), AccountMeta::new(validator_stake_account.stake_account, false),
AccountMeta::new(stake_pool_accounts.reserve_stake.pubkey(), false), AccountMeta::new(stake_pool_accounts.reserve_stake.pubkey(), false),
AccountMeta::new(pool_token_account, 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(stake_pool_accounts.pool_mint.pubkey(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::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 { let instruction = Instruction {
program_id: id(), program_id: id(),
accounts, accounts,
data: instruction::StakePoolInstruction::Deposit data: instruction::StakePoolInstruction::DepositStake
.try_to_vec() .try_to_vec()
.unwrap(), .unwrap(),
}; };
@ -325,7 +329,7 @@ async fn fail_with_wrong_token_program_id() {
let wrong_token_program = Keypair::new(); let wrong_token_program = Keypair::new();
let mut transaction = Transaction::new_with_payer( let mut transaction = Transaction::new_with_payer(
&instruction::deposit( &instruction::deposit_stake(
&id(), &id(),
&stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.validator_list.pubkey(), &stake_pool_accounts.validator_list.pubkey(),
@ -335,6 +339,8 @@ async fn fail_with_wrong_token_program_id() {
&validator_stake_account.stake_account, &validator_stake_account.stake_account,
&stake_pool_accounts.reserve_stake.pubkey(), &stake_pool_accounts.reserve_stake.pubkey(),
&pool_token_account, &pool_token_account,
&stake_pool_accounts.pool_fee_account.pubkey(),
&stake_pool_accounts.pool_fee_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(), &stake_pool_accounts.pool_mint.pubkey(),
&wrong_token_program.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; let program_error = token_error::TokenError::MintMismatch as u32;
assert_eq!(error_index, program_error); 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 async fn fail_with_out_of_dated_pool_balances() {} // TODO
#[tokio::test] #[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 (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let deposit_authority = Keypair::new(); let stake_deposit_authority = Keypair::new();
let stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority); let stake_pool_accounts =
StakePoolAccounts::new_with_stake_deposit_authority(stake_deposit_authority);
stake_pool_accounts stake_pool_accounts
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
.await .await
@ -659,10 +668,11 @@ async fn success_with_deposit_authority() {
} }
#[tokio::test] #[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 (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let deposit_authority = Keypair::new(); let stake_deposit_authority = Keypair::new();
let mut stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority); let mut stake_pool_accounts =
StakePoolAccounts::new_with_stake_deposit_authority(stake_deposit_authority);
stake_pool_accounts stake_pool_accounts
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
.await .await
@ -726,8 +736,8 @@ async fn fail_without_deposit_authority_signature() {
.unwrap(); .unwrap();
let wrong_depositor = Keypair::new(); let wrong_depositor = Keypair::new();
stake_pool_accounts.deposit_authority = wrong_depositor.pubkey(); stake_pool_accounts.stake_deposit_authority = wrong_depositor.pubkey();
stake_pool_accounts.deposit_authority_keypair = Some(wrong_depositor); stake_pool_accounts.stake_deposit_authority_keypair = Some(wrong_depositor);
let error = stake_pool_accounts let error = stake_pool_accounts
.deposit_stake( .deposit_stake(
@ -747,7 +757,7 @@ async fn fail_without_deposit_authority_signature() {
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
assert_eq!( assert_eq!(
error_index, 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"), _ => 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"), _ => 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"
),
}
}

View File

@ -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::<state::StakePool>(&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::<state::StakePool>(&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"
),
}
}

View File

@ -266,9 +266,11 @@ pub async fn create_stake_pool(
pool_token_account: &Pubkey, pool_token_account: &Pubkey,
manager: &Keypair, manager: &Keypair,
staker: &Pubkey, staker: &Pubkey,
deposit_authority: &Option<Keypair>, stake_deposit_authority: &Option<Keypair>,
fee: &state::Fee, fee: &state::Fee,
withdrawal_fee: &state::Fee, withdrawal_fee: &state::Fee,
deposit_fee: &state::Fee,
referral_fee: u8,
max_validators: u32, max_validators: u32,
) -> Result<(), TransportError> { ) -> Result<(), TransportError> {
let rent = banks_client.get_rent().await.unwrap(); let rent = banks_client.get_rent().await.unwrap();
@ -303,17 +305,19 @@ pub async fn create_stake_pool(
pool_mint, pool_mint,
pool_token_account, pool_token_account,
&spl_token::id(), &spl_token::id(),
deposit_authority.as_ref().map(|k| k.pubkey()), stake_deposit_authority.as_ref().map(|k| k.pubkey()),
*fee, *fee,
*withdrawal_fee, *withdrawal_fee,
*deposit_fee,
referral_fee,
max_validators, max_validators,
), ),
], ],
Some(&payer.pubkey()), Some(&payer.pubkey()),
); );
let mut signers = vec![payer, stake_pool, validator_list, manager]; let mut signers = vec![payer, stake_pool, validator_list, manager];
if let Some(deposit_authority) = deposit_authority.as_ref() { if let Some(stake_deposit_authority) = stake_deposit_authority.as_ref() {
signers.push(deposit_authority); signers.push(stake_deposit_authority);
} }
transaction.sign(&signers, *recent_blockhash); transaction.sign(&signers, *recent_blockhash);
banks_client.process_transaction(transaction).await?; banks_client.process_transaction(transaction).await?;
@ -542,10 +546,12 @@ pub struct StakePoolAccounts {
pub manager: Keypair, pub manager: Keypair,
pub staker: Keypair, pub staker: Keypair,
pub withdraw_authority: Pubkey, pub withdraw_authority: Pubkey,
pub deposit_authority: Pubkey, pub stake_deposit_authority: Pubkey,
pub deposit_authority_keypair: Option<Keypair>, pub stake_deposit_authority_keypair: Option<Keypair>,
pub fee: state::Fee, pub fee: state::Fee,
pub withdrawal_fee: state::Fee, pub withdrawal_fee: state::Fee,
pub deposit_fee: state::Fee,
pub referral_fee: u8,
pub max_validators: u32, pub max_validators: u32,
} }
@ -558,7 +564,7 @@ impl StakePoolAccounts {
&[&stake_pool_address.to_bytes()[..32], b"withdraw"], &[&stake_pool_address.to_bytes()[..32], b"withdraw"],
&id(), &id(),
); );
let (deposit_authority, _) = Pubkey::find_program_address( let (stake_deposit_authority, _) = Pubkey::find_program_address(
&[&stake_pool_address.to_bytes()[..32], b"deposit"], &[&stake_pool_address.to_bytes()[..32], b"deposit"],
&id(), &id(),
); );
@ -577,8 +583,8 @@ impl StakePoolAccounts {
manager, manager,
staker, staker,
withdraw_authority, withdraw_authority,
deposit_authority, stake_deposit_authority,
deposit_authority_keypair: None, stake_deposit_authority_keypair: None,
fee: state::Fee { fee: state::Fee {
numerator: 1, numerator: 1,
denominator: 100, denominator: 100,
@ -587,14 +593,19 @@ impl StakePoolAccounts {
numerator: 3, numerator: 3,
denominator: 1000, denominator: 1000,
}, },
deposit_fee: state::Fee {
numerator: 1,
denominator: 1000,
},
referral_fee: 25,
max_validators: MAX_TEST_VALIDATORS, 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(); let mut stake_pool_accounts = Self::new();
stake_pool_accounts.deposit_authority = deposit_authority.pubkey(); stake_pool_accounts.stake_deposit_authority = stake_deposit_authority.pubkey();
stake_pool_accounts.deposit_authority_keypair = Some(deposit_authority); stake_pool_accounts.stake_deposit_authority_keypair = Some(stake_deposit_authority);
stake_pool_accounts stake_pool_accounts
} }
@ -606,6 +617,14 @@ impl StakePoolAccounts {
pool_tokens * self.withdrawal_fee.numerator / self.withdrawal_fee.denominator 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( pub async fn initialize_stake_pool(
&self, &self,
mut banks_client: &mut BanksClient, mut banks_client: &mut BanksClient,
@ -654,9 +673,11 @@ impl StakePoolAccounts {
&self.pool_fee_account.pubkey(), &self.pool_fee_account.pubkey(),
&self.manager, &self.manager,
&self.staker.pubkey(), &self.staker.pubkey(),
&self.deposit_authority_keypair, &self.stake_deposit_authority_keypair,
&self.fee, &self.fee,
&self.withdrawal_fee, &self.withdrawal_fee,
&self.deposit_fee,
self.referral_fee,
self.max_validators, self.max_validators,
) )
.await?; .await?;
@ -675,25 +696,27 @@ impl StakePoolAccounts {
current_staker: &Keypair, current_staker: &Keypair,
) -> Option<TransportError> { ) -> Option<TransportError> {
let mut signers = vec![payer, current_staker]; let mut signers = vec![payer, current_staker];
let instructions = if let Some(deposit_authority) = self.deposit_authority_keypair.as_ref() let instructions =
{ if let Some(stake_deposit_authority) = self.stake_deposit_authority_keypair.as_ref() {
signers.push(deposit_authority); signers.push(stake_deposit_authority);
instruction::deposit_with_authority( instruction::deposit_stake_with_authority(
&id(), &id(),
&self.stake_pool.pubkey(), &self.stake_pool.pubkey(),
&self.validator_list.pubkey(), &self.validator_list.pubkey(),
&self.deposit_authority, &self.stake_deposit_authority,
&self.withdraw_authority, &self.withdraw_authority,
stake, stake,
&current_staker.pubkey(), &current_staker.pubkey(),
validator_stake_account, validator_stake_account,
&self.reserve_stake.pubkey(), &self.reserve_stake.pubkey(),
pool_account, pool_account,
&self.pool_fee_account.pubkey(),
&self.pool_fee_account.pubkey(),
&self.pool_mint.pubkey(), &self.pool_mint.pubkey(),
&spl_token::id(), &spl_token::id(),
) )
} else { } else {
instruction::deposit( instruction::deposit_stake(
&id(), &id(),
&self.stake_pool.pubkey(), &self.stake_pool.pubkey(),
&self.validator_list.pubkey(), &self.validator_list.pubkey(),
@ -703,6 +726,8 @@ impl StakePoolAccounts {
validator_stake_account, validator_stake_account,
&self.reserve_stake.pubkey(), &self.reserve_stake.pubkey(),
pool_account, pool_account,
&self.pool_fee_account.pubkey(),
&self.pool_fee_account.pubkey(),
&self.pool_mint.pubkey(), &self.pool_mint.pubkey(),
&spl_token::id(), &spl_token::id(),
) )
@ -716,6 +741,57 @@ impl StakePoolAccounts {
banks_client.process_transaction(transaction).await.err() 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<TransportError> {
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(),
&sol_deposit_authority.pubkey(),
&self.withdraw_authority,
&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_sol(
&id(),
&self.stake_pool.pubkey(),
&self.withdraw_authority,
&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(
&instructions,
Some(&payer.pubkey()),
&signers,
*recent_blockhash,
);
banks_client.process_transaction(transaction).await.err()
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn withdraw_stake( pub async fn withdraw_stake(
&self, &self,
@ -730,7 +806,7 @@ impl StakePoolAccounts {
amount: u64, amount: u64,
) -> Option<TransportError> { ) -> Option<TransportError> {
let transaction = Transaction::new_signed_with_payer( let transaction = Transaction::new_signed_with_payer(
&[instruction::withdraw( &[instruction::withdraw_stake(
&id(), &id(),
&self.stake_pool.pubkey(), &self.stake_pool.pubkey(),
&self.validator_list.pubkey(), &self.validator_list.pubkey(),
@ -1084,7 +1160,7 @@ impl DepositStakeAccount {
.await; .await;
} }
pub async fn deposit( pub async fn deposit_stake(
&mut self, &mut self,
banks_client: &mut BanksClient, banks_client: &mut BanksClient,
payer: &Keypair, payer: &Keypair,
@ -1119,7 +1195,7 @@ impl DepositStakeAccount {
} }
} }
pub async fn simple_deposit( pub async fn simple_deposit_stake(
banks_client: &mut BanksClient, banks_client: &mut BanksClient,
payer: &Keypair, payer: &Keypair,
recent_blockhash: &Hash, recent_blockhash: &Hash,

View File

@ -32,7 +32,7 @@ use {
spl_token::state::{Account as SplAccount, AccountState as SplAccountState, Mint}, 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 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_AMOUNT: u64 = 200_000_000_000;
const STAKE_ACCOUNT_RENT_EXEMPTION: u64 = 2_282_880; const STAKE_ACCOUNT_RENT_EXEMPTION: u64 = 2_282_880;
@ -56,15 +56,15 @@ async fn setup(
stake_pool_accounts.max_validators = max_validators; stake_pool_accounts.max_validators = max_validators;
let stake_pool_pubkey = stake_pool_accounts.stake_pool.pubkey(); 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); find_withdraw_authority_program_address(&id(), &stake_pool_pubkey);
let mut stake_pool = StakePool { let mut stake_pool = StakePool {
account_type: AccountType::StakePool, account_type: AccountType::StakePool,
manager: stake_pool_accounts.manager.pubkey(), manager: stake_pool_accounts.manager.pubkey(),
staker: stake_pool_accounts.staker.pubkey(), staker: stake_pool_accounts.staker.pubkey(),
deposit_authority: stake_pool_accounts.deposit_authority, stake_deposit_authority: stake_pool_accounts.stake_deposit_authority,
withdraw_bump_seed, stake_withdraw_bump_seed,
validator_list: stake_pool_accounts.validator_list.pubkey(), validator_list: stake_pool_accounts.validator_list.pubkey(),
reserve_stake: stake_pool_accounts.reserve_stake.pubkey(), reserve_stake: stake_pool_accounts.reserve_stake.pubkey(),
pool_mint: stake_pool_accounts.pool_mint.pubkey(), pool_mint: stake_pool_accounts.pool_mint.pubkey(),
@ -78,10 +78,13 @@ async fn setup(
next_epoch_fee: None, next_epoch_fee: None,
preferred_deposit_validator_vote_address: None, preferred_deposit_validator_vote_address: None,
preferred_withdraw_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(), withdrawal_fee: Fee::default(),
next_withdrawal_fee: None, 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); let mut validator_list = ValidatorList::new(max_validators);
@ -592,7 +595,7 @@ async fn add_validator_to_pool() {
increase_amount, increase_amount,
) )
.await; .await;
assert!(error.is_none()); assert!(error.is_none(), "{:?}", error);
let validator_list = get_account( let validator_list = get_account(
&mut context.banks_client, &mut context.banks_client,
@ -655,7 +658,7 @@ async fn set_preferred() {
} }
#[tokio::test] #[tokio::test]
async fn deposit() { async fn deposit_stake() {
let (mut context, stake_pool_accounts, _, vote_pubkey, user, stake_pubkey, pool_account_pubkey) = let (mut context, stake_pool_accounts, _, vote_pubkey, user, stake_pubkey, pool_account_pubkey) =
setup(HUGE_POOL_SIZE, HUGE_POOL_SIZE, STAKE_AMOUNT).await; setup(HUGE_POOL_SIZE, HUGE_POOL_SIZE, STAKE_AMOUNT).await;

View File

@ -46,7 +46,7 @@ async fn setup() -> (
) )
.await; .await;
let _deposit_info = simple_deposit( let _deposit_info = simple_deposit_stake(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,

View File

@ -254,6 +254,8 @@ async fn fail_with_wrong_max_validators() {
None, None,
stake_pool_accounts.fee, stake_pool_accounts.fee,
stake_pool_accounts.withdrawal_fee, stake_pool_accounts.withdrawal_fee,
stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
), ),
], ],
@ -325,6 +327,8 @@ async fn fail_with_wrong_mint_authority() {
&None, &None,
&stake_pool_accounts.fee, &stake_pool_accounts.fee,
&stake_pool_accounts.withdrawal_fee, &stake_pool_accounts.withdrawal_fee,
&stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
) )
.await .await
@ -411,6 +415,8 @@ async fn fail_with_freeze_authority() {
&None, &None,
&stake_pool_accounts.fee, &stake_pool_accounts.fee,
&stake_pool_accounts.withdrawal_fee, &stake_pool_accounts.withdrawal_fee,
&stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
) )
.await .await
@ -499,6 +505,8 @@ async fn fail_with_wrong_token_program_id() {
None, None,
stake_pool_accounts.fee, stake_pool_accounts.fee,
stake_pool_accounts.withdrawal_fee, stake_pool_accounts.withdrawal_fee,
stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
), ),
], ],
@ -576,6 +584,8 @@ async fn fail_with_wrong_fee_account() {
&None, &None,
&stake_pool_accounts.fee, &stake_pool_accounts.fee,
&stake_pool_accounts.withdrawal_fee, &stake_pool_accounts.withdrawal_fee,
&stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
) )
.await .await
@ -665,6 +675,8 @@ async fn fail_with_not_rent_exempt_pool() {
None, None,
stake_pool_accounts.fee, stake_pool_accounts.fee,
stake_pool_accounts.withdrawal_fee, stake_pool_accounts.withdrawal_fee,
stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
), ),
], ],
@ -740,6 +752,8 @@ async fn fail_with_not_rent_exempt_validator_list() {
None, None,
stake_pool_accounts.fee, stake_pool_accounts.fee,
stake_pool_accounts.withdrawal_fee, stake_pool_accounts.withdrawal_fee,
stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
), ),
], ],
@ -792,6 +806,8 @@ async fn fail_without_manager_signature() {
let init_data = instruction::StakePoolInstruction::Initialize { let init_data = instruction::StakePoolInstruction::Initialize {
fee: stake_pool_accounts.fee, fee: stake_pool_accounts.fee,
withdrawal_fee: stake_pool_accounts.withdrawal_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, max_validators: stake_pool_accounts.max_validators,
}; };
let data = init_data.try_to_vec().unwrap(); let data = init_data.try_to_vec().unwrap();
@ -914,6 +930,8 @@ async fn fail_with_pre_minted_pool_tokens() {
&None, &None,
&stake_pool_accounts.fee, &stake_pool_accounts.fee,
&stake_pool_accounts.withdrawal_fee, &stake_pool_accounts.withdrawal_fee,
&stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
) )
.await .await
@ -976,6 +994,8 @@ async fn fail_with_bad_reserve() {
&None, &None,
&stake_pool_accounts.fee, &stake_pool_accounts.fee,
&stake_pool_accounts.withdrawal_fee, &stake_pool_accounts.withdrawal_fee,
&stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
) )
.await .await
@ -1022,6 +1042,8 @@ async fn fail_with_bad_reserve() {
&None, &None,
&stake_pool_accounts.fee, &stake_pool_accounts.fee,
&stake_pool_accounts.withdrawal_fee, &stake_pool_accounts.withdrawal_fee,
&stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
) )
.await .await
@ -1071,6 +1093,8 @@ async fn fail_with_bad_reserve() {
&None, &None,
&stake_pool_accounts.fee, &stake_pool_accounts.fee,
&stake_pool_accounts.withdrawal_fee, &stake_pool_accounts.withdrawal_fee,
&stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
) )
.await .await
@ -1120,6 +1144,8 @@ async fn fail_with_bad_reserve() {
&None, &None,
&stake_pool_accounts.fee, &stake_pool_accounts.fee,
&stake_pool_accounts.withdrawal_fee, &stake_pool_accounts.withdrawal_fee,
&stake_pool_accounts.deposit_fee,
stake_pool_accounts.referral_fee,
stake_pool_accounts.max_validators, stake_pool_accounts.max_validators,
) )
.await .await
@ -1138,10 +1164,11 @@ async fn fail_with_bad_reserve() {
} }
#[tokio::test] #[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 (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let deposit_authority = Keypair::new(); let stake_deposit_authority = Keypair::new();
let stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority); let stake_pool_accounts =
StakePoolAccounts::new_with_stake_deposit_authority(stake_deposit_authority);
stake_pool_accounts stake_pool_accounts
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1) .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
.await .await
@ -1153,7 +1180,7 @@ async fn success_with_required_deposit_authority() {
let stake_pool = let stake_pool =
try_from_slice_unchecked::<state::StakePool>(stake_pool_account.data.as_slice()).unwrap(); try_from_slice_unchecked::<state::StakePool>(stake_pool_account.data.as_slice()).unwrap();
assert_eq!( assert_eq!(
stake_pool.deposit_authority, stake_pool.stake_deposit_authority,
stake_pool_accounts.deposit_authority stake_pool_accounts.stake_deposit_authority
); );
} }

View File

@ -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::<state::StakePool>(&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::<state::StakePool>(&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::<state::StakePool>(&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::<state::StakePool>(&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::<state::StakePool>(&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::<state::StakePool>(&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"),
}
}

View File

@ -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<Fee>) -> (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::<StakePool>(&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::<StakePool>(&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::<StakePool>(&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"),
}
}

View File

@ -50,7 +50,7 @@ async fn success() {
let old_fee = stake_pool.fee; let old_fee = stake_pool.fee;
let transaction = Transaction::new_signed_with_payer( let transaction = Transaction::new_signed_with_payer(
&[instruction::set_fee( &[instruction::set_epoch_fee(
&id(), &id(),
&stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.manager.pubkey(), &stake_pool_accounts.manager.pubkey(),
@ -108,7 +108,7 @@ async fn fail_wrong_manager() {
let wrong_manager = Keypair::new(); let wrong_manager = Keypair::new();
let transaction = Transaction::new_signed_with_payer( let transaction = Transaction::new_signed_with_payer(
&[instruction::set_fee( &[instruction::set_epoch_fee(
&id(), &id(),
&stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.stake_pool.pubkey(),
&wrong_manager.pubkey(), &wrong_manager.pubkey(),
@ -144,7 +144,7 @@ async fn fail_high_fee() {
denominator: 10, denominator: 10,
}; };
let transaction = Transaction::new_signed_with_payer( let transaction = Transaction::new_signed_with_payer(
&[instruction::set_fee( &[instruction::set_epoch_fee(
&id(), &id(),
&stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.manager.pubkey(), &stake_pool_accounts.manager.pubkey(),
@ -193,7 +193,7 @@ async fn fail_not_updated() {
context.warp_to_slot(50_000).unwrap(); context.warp_to_slot(50_000).unwrap();
let transaction = Transaction::new_signed_with_payer( let transaction = Transaction::new_signed_with_payer(
&[instruction::set_fee( &[instruction::set_epoch_fee(
&id(), &id(),
&stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.manager.pubkey(), &stake_pool_accounts.manager.pubkey(),

View File

@ -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<u8>) -> (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::<StakePool>(&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::<StakePool>(&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::<StakePool>(&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"),
}
}

View File

@ -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] #[tokio::test]
async fn fail_not_updated() { async fn fail_not_updated() {
let mut context = program_test().start_with_context().await; let mut context = program_test().start_with_context().await;

View File

@ -44,7 +44,7 @@ async fn setup() -> (
) )
.await; .await;
let _deposit_info = simple_deposit( let _deposit_info = simple_deposit_stake(
&mut context.banks_client, &mut context.banks_client,
&context.payer, &context.payer,
&context.last_blockhash, &context.last_blockhash,
@ -65,6 +65,12 @@ async fn setup() -> (
async fn success() { async fn success() {
let (mut context, stake_pool_accounts, stake_accounts) = setup().await; 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( let pre_balance = get_validator_list_sum(
&mut context.banks_client, &mut context.banks_client,
&stake_pool_accounts.reserve_stake.pubkey(), &stake_pool_accounts.reserve_stake.pubkey(),
@ -134,7 +140,7 @@ async fn success() {
let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool.data.as_slice()).unwrap(); let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool.data.as_slice()).unwrap();
assert_eq!(post_balance, stake_pool.total_stake_lamports); 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, &mut context.banks_client,
&stake_pool_accounts.pool_fee_account.pubkey(), &stake_pool_accounts.pool_fee_account.pubkey(),
) )
@ -144,6 +150,7 @@ async fn success() {
&stake_pool_accounts.pool_mint.pubkey(), &stake_pool_accounts.pool_mint.pubkey(),
) )
.await; .await;
let actual_fee = post_fee - pre_fee;
assert_eq!(pool_token_supply - pre_token_supply, actual_fee); assert_eq!(pool_token_supply - pre_token_supply, actual_fee);
let expected_fee_lamports = let expected_fee_lamports =

View File

@ -103,7 +103,7 @@ async fn setup(
for deposit_account in &mut deposit_accounts { for deposit_account in &mut deposit_accounts {
deposit_account deposit_account
.deposit( .deposit_stake(
&mut context.banks_client, &mut context.banks_client,
&context.payer, &context.payer,
&context.last_blockhash, &context.last_blockhash,

View File

@ -434,7 +434,7 @@ async fn success_with_deactivating_transient_stake() {
let rent = banks_client.get_rent().await.unwrap(); let rent = banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>()); let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let deposit_info = simple_deposit( let deposit_info = simple_deposit_stake(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,
@ -472,7 +472,7 @@ async fn success_with_deactivating_transient_stake() {
assert!(error.is_none()); assert!(error.is_none());
// fail deposit // fail deposit
let maybe_deposit = simple_deposit( let maybe_deposit = simple_deposit_stake(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,

View File

@ -51,7 +51,7 @@ async fn setup() -> (
) )
.await; .await;
let deposit_info = simple_deposit( let deposit_info = simple_deposit_stake(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,
@ -274,7 +274,7 @@ async fn fail_with_wrong_stake_program() {
let instruction = Instruction { let instruction = Instruction {
program_id: id(), program_id: id(),
accounts, accounts,
data: instruction::StakePoolInstruction::Withdraw(tokens_to_burn) data: instruction::StakePoolInstruction::WithdrawStake(tokens_to_burn)
.try_to_vec() .try_to_vec()
.unwrap(), .unwrap(),
}; };
@ -361,7 +361,7 @@ async fn fail_with_wrong_token_program_id() {
let wrong_token_program = Keypair::new(); let wrong_token_program = Keypair::new();
let transaction = Transaction::new_signed_with_payer( let transaction = Transaction::new_signed_with_payer(
&[instruction::withdraw( &[instruction::withdraw_stake(
&id(), &id(),
&stake_pool_accounts.stake_pool.pubkey(), &stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.validator_list.pubkey(), &stake_pool_accounts.validator_list.pubkey(),
@ -475,6 +475,67 @@ async fn fail_with_unknown_validator() {
) )
.await; .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 new_authority = Pubkey::new_unique();
let transaction_error = stake_pool_accounts let transaction_error = stake_pool_accounts
.withdraw_stake( .withdraw_stake(
@ -585,7 +646,7 @@ async fn fail_without_token_approval() {
) )
.await; .await;
let deposit_info = simple_deposit( let deposit_info = simple_deposit_stake(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,
@ -656,7 +717,7 @@ async fn fail_with_low_delegation() {
) )
.await; .await;
let deposit_info = simple_deposit( let deposit_info = simple_deposit_stake(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,
@ -796,7 +857,7 @@ async fn success_with_reserve() {
let rent = context.banks_client.get_rent().await.unwrap(); let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>()); let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let deposit_info = simple_deposit( let deposit_info = simple_deposit_stake(
&mut context.banks_client, &mut context.banks_client,
&context.payer, &context.payer,
&context.last_blockhash, &context.last_blockhash,
@ -930,10 +991,12 @@ async fn success_with_reserve() {
assert!(error.is_none()); assert!(error.is_none());
// first and only deposit, lamports:pool 1:1 // 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 = let tokens_withdrawal_fee =
stake_pool_accounts.calculate_withdrawal_fee(deposit_info.pool_tokens); stake_pool_accounts.calculate_withdrawal_fee(deposit_info.pool_tokens);
assert_eq!( assert_eq!(
deposit_info.stake_lamports + stake_rent, deposit_info.stake_lamports + stake_rent - tokens_deposit_fee,
deposit_info.pool_tokens, deposit_info.pool_tokens,
); );
@ -954,8 +1017,12 @@ async fn success_with_reserve() {
let stake_state = let stake_state =
deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap(); deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap();
let meta = stake_state.meta().unwrap(); let meta = stake_state.meta().unwrap();
// TODO: these numbers dont add up even with +tokens_deposit_fee
assert_eq!( 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 reserve_stake_account.lamports
); );
@ -964,7 +1031,9 @@ async fn success_with_reserve() {
get_account(&mut context.banks_client, &withdraw_destination.pubkey()).await; get_account(&mut context.banks_client, &withdraw_destination.pubkey()).await;
assert_eq!( assert_eq!(
user_stake_recipient_account.lamports, 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()); assert!(error.is_none());
// deposit into preferred, then fail // deposit into preferred, then fail
let _preferred_deposit = simple_deposit( let _preferred_deposit = simple_deposit_stake(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,
@ -1152,7 +1221,7 @@ async fn success_withdraw_from_transient() {
let rent = context.banks_client.get_rent().await.unwrap(); let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>()); let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let deposit_info = simple_deposit( let deposit_info = simple_deposit_stake(
&mut context.banks_client, &mut context.banks_client,
&context.payer, &context.payer,
&context.last_blockhash, &context.last_blockhash,