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

View File

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

View File

@ -108,6 +108,14 @@ pub enum StakePoolError {
/// Not enough lamports provided for deposit to result in one pool token
#[error("DepositTooSmall")]
DepositTooSmall,
// 30.
/// Provided stake deposit authority does not match the program's
#[error("FeeIncreaseTooHigh")]
InvalidStakeDepositAuthority,
/// Provided sol deposit authority does not match the program's
#[error("InvalidSolDepositAuthority")]
InvalidSolDepositAuthority,
}
impl From<StakePoolError> for ProgramError {
fn from(e: StakePoolError) -> Self {

View File

@ -6,7 +6,7 @@ use {
find_deposit_authority_program_address, find_stake_program_address,
find_transient_stake_program_address, find_withdraw_authority_program_address,
stake_program,
state::{Fee, StakePool, ValidatorList},
state::{Fee, FeeType, StakePool, ValidatorList},
MAX_VALIDATORS_TO_UPDATE,
},
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
@ -28,6 +28,17 @@ pub enum PreferredValidatorType {
Withdraw,
}
/// Defines which validator vote account is set during the
/// `SetPreferredValidator` instruction
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
pub enum DepositType {
/// Set preferred validator for deposits
Stake,
/// Set preferred validator for withdraws
Sol,
}
/// Instructions supported by the StakePool program.
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
@ -55,6 +66,12 @@ pub enum StakePoolInstruction {
/// Fee charged per withdrawal as percentage of withdrawal
#[allow(dead_code)] // but it's not
withdrawal_fee: Fee,
/// Fee charged per deposit as percentage of deposit
#[allow(dead_code)] // but it's not
deposit_fee: Fee,
/// Percentage [0-100] of deposit_fee that goes to referrer
#[allow(dead_code)] // but it's not
referral_fee: u8,
/// Maximum expected number of validators
#[allow(dead_code)] // but it's not
max_validators: u32,
@ -244,18 +261,20 @@ pub enum StakePoolInstruction {
///
/// 0. `[w]` Stake pool
/// 1. `[w]` Validator stake list storage account
/// 2. `[]` Stake pool deposit authority
/// 2. `[s]/[]` Stake pool deposit authority
/// 3. `[]` Stake pool withdraw authority
/// 4. `[w]` Stake account to join the pool (withdraw authority for the stake account should be first set to the stake pool deposit authority)
/// 5. `[w]` Validator stake account for the stake account to be merged with
/// 6. `[w]` Reserve stake account, to withdraw rent exempt reserve
/// 7. `[w]` User account to receive pool tokens
/// 8. `[w]` Pool token mint account
/// 9. '[]' Sysvar clock account
/// 10. '[]' Sysvar stake history account
/// 11. `[]` Pool token program id,
/// 12. `[]` Stake program id,
Deposit,
/// 8. `[w]` Account to receive pool fee tokens
/// 9. `[w]` Account to receive a portion of pool fee tokens as referral fees
/// 10. `[w]` Pool token mint account
/// 11. '[]' Sysvar clock account
/// 12. '[]' Sysvar stake history account
/// 13. `[]` Pool token program id,
/// 14. `[]` Stake program id,
DepositStake,
/// Withdraw the token from the pool at the current ratio.
///
@ -281,7 +300,7 @@ pub enum StakePoolInstruction {
/// 11. `[]` Pool token program id
/// 12. `[]` Stake program id,
/// userdata: amount of pool tokens to withdraw
Withdraw(u64),
WithdrawStake(u64),
/// (Manager only) Update manager
///
@ -297,9 +316,9 @@ pub enum StakePoolInstruction {
/// 1. `[s]` Manager
/// 2. `[]` Sysvar clock
SetFee {
/// Fee assessed as percentage of perceived rewards
/// Type of fee to update and value to update it to
#[allow(dead_code)] // but it's not
fee: Fee,
fee: FeeType,
},
/// (Manager or staker only) Update staker
@ -309,16 +328,29 @@ pub enum StakePoolInstruction {
/// 2. '[]` New staker pubkey
SetStaker,
/// (Manager only) Update Withdrawal fee for next epoch
/// Deposit SOL directly into the pool's reserve account. The output is a "pool" token
/// representing ownership into the pool. Inputs are converted to the current ratio.
///
/// 0. `[w]` Stake pool
/// 1. `[s]/[]` Stake pool sol deposit authority.
/// 2. `[]` Stake pool withdraw authority
/// 3. `[w]` Reserve stake account, to withdraw rent exempt reserve
/// 4. `[s]` Account providing the lamports to be deposited into the pool
/// 5. `[w]` User account to receive pool tokens
/// 6. `[w]` Account to receive pool fee tokens
/// 7. `[w]` Account to receive a portion of pool fee tokens as referral fees
/// 8. `[w]` Pool token mint account
/// 9. '[]' Sysvar clock account
/// 10 `[]` System program account
/// 11. `[]` Pool token program id,
DepositSol(u64),
/// (Manager only) Update SOL deposit authority
///
/// 0. `[w]` StakePool
/// 1. `[s]` Manager
/// 2. `[]` Sysvar clock
SetWithdrawalFee {
/// Fee assessed as percentage of perceived rewards
#[allow(dead_code)] // but it's not
fee: Fee,
},
/// 2. '[]` New sol_deposit_authority pubkey or none
SetDepositAuthority(DepositType),
}
/// Creates an 'initialize' instruction.
@ -335,11 +367,15 @@ pub fn initialize(
deposit_authority: Option<Pubkey>,
fee: Fee,
withdrawal_fee: Fee,
deposit_fee: Fee,
referral_fee: u8,
max_validators: u32,
) -> Instruction {
let init_data = StakePoolInstruction::Initialize {
fee,
withdrawal_fee,
deposit_fee,
referral_fee,
max_validators,
};
let data = init_data.try_to_vec().unwrap();
@ -826,7 +862,7 @@ pub fn update_stake_pool(
/// Creates instructions required to deposit into a stake pool, given a stake
/// account owned by the user.
pub fn deposit(
pub fn deposit_stake(
program_id: &Pubkey,
stake_pool: &Pubkey,
validator_list_storage: &Pubkey,
@ -836,6 +872,8 @@ pub fn deposit(
validator_stake_account: &Pubkey,
reserve_stake_account: &Pubkey,
pool_tokens_to: &Pubkey,
manager_fee_account: &Pubkey,
referrer_pool_tokens_account: &Pubkey,
pool_mint: &Pubkey,
token_program_id: &Pubkey,
) -> Vec<Instruction> {
@ -850,6 +888,8 @@ pub fn deposit(
AccountMeta::new(*validator_stake_account, false),
AccountMeta::new(*reserve_stake_account, false),
AccountMeta::new(*pool_tokens_to, false),
AccountMeta::new(*manager_fee_account, false),
AccountMeta::new(*referrer_pool_tokens_account, false),
AccountMeta::new(*pool_mint, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
@ -872,7 +912,7 @@ pub fn deposit(
Instruction {
program_id: *program_id,
accounts,
data: StakePoolInstruction::Deposit.try_to_vec().unwrap(),
data: StakePoolInstruction::DepositStake.try_to_vec().unwrap(),
},
]
}
@ -880,7 +920,7 @@ pub fn deposit(
/// Creates instructions required to deposit into a stake pool, given a stake
/// account owned by the user. The difference with `deposit()` is that a deposit
/// authority must sign this instruction, which is required for private pools.
pub fn deposit_with_authority(
pub fn deposit_stake_with_authority(
program_id: &Pubkey,
stake_pool: &Pubkey,
validator_list_storage: &Pubkey,
@ -891,6 +931,8 @@ pub fn deposit_with_authority(
validator_stake_account: &Pubkey,
reserve_stake_account: &Pubkey,
pool_tokens_to: &Pubkey,
manager_fee_account: &Pubkey,
referrer_pool_tokens_account: &Pubkey,
pool_mint: &Pubkey,
token_program_id: &Pubkey,
) -> Vec<Instruction> {
@ -903,6 +945,8 @@ pub fn deposit_with_authority(
AccountMeta::new(*validator_stake_account, false),
AccountMeta::new(*reserve_stake_account, false),
AccountMeta::new(*pool_tokens_to, false),
AccountMeta::new(*manager_fee_account, false),
AccountMeta::new(*referrer_pool_tokens_account, false),
AccountMeta::new(*pool_mint, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
@ -925,13 +969,91 @@ pub fn deposit_with_authority(
Instruction {
program_id: *program_id,
accounts,
data: StakePoolInstruction::Deposit.try_to_vec().unwrap(),
data: StakePoolInstruction::DepositStake.try_to_vec().unwrap(),
},
]
}
/// Creates a 'withdraw' instruction.
pub fn withdraw(
/// Creates instructions required to deposit SOL directly into a stake pool.
pub fn deposit_sol(
program_id: &Pubkey,
stake_pool: &Pubkey,
stake_pool_withdraw_authority: &Pubkey,
reserve_stake_account: &Pubkey,
lamports_from: &Pubkey,
pool_tokens_to: &Pubkey,
manager_fee_account: &Pubkey,
referrer_pool_tokens_account: &Pubkey,
pool_mint: &Pubkey,
token_program_id: &Pubkey,
amount: u64,
) -> Vec<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,
stake_pool: &Pubkey,
validator_list_storage: &Pubkey,
@ -964,7 +1086,9 @@ pub fn withdraw(
Instruction {
program_id: *program_id,
accounts,
data: StakePoolInstruction::Withdraw(amount).try_to_vec().unwrap(),
data: StakePoolInstruction::WithdrawStake(amount)
.try_to_vec()
.unwrap(),
}
}
@ -994,7 +1118,7 @@ pub fn set_fee(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
fee: Fee,
fee: FeeType,
) -> Instruction {
let accounts = vec![
AccountMeta::new(*stake_pool, false),
@ -1008,25 +1132,64 @@ pub fn set_fee(
}
}
/// Creates a 'set fee' instruction.
/// Creates a 'set fee' instruction for setting the epoch fee
pub fn set_epoch_fee(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
fee: Fee,
) -> Instruction {
set_fee(program_id, stake_pool, manager, FeeType::Epoch(fee))
}
/// Creates a 'set fee' instruction for setting withdrawal fee
pub fn set_withdrawal_fee(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
fee: Fee,
) -> Instruction {
let accounts = vec![
AccountMeta::new(*stake_pool, false),
AccountMeta::new_readonly(*manager, true),
AccountMeta::new_readonly(sysvar::clock::id(), false),
];
Instruction {
program_id: *program_id,
accounts,
data: StakePoolInstruction::SetWithdrawalFee { fee }
.try_to_vec()
.unwrap(),
}
set_fee(program_id, stake_pool, manager, FeeType::Withdrawal(fee))
}
/// Creates a 'set fee' instruction for setting SOL deposit fee
pub fn set_sol_deposit_fee(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
fee: Fee,
) -> Instruction {
set_fee(program_id, stake_pool, manager, FeeType::SolDeposit(fee))
}
/// Creates a 'set fee' instruction for setting stake deposit fee
pub fn set_stake_deposit_fee(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
fee: Fee,
) -> Instruction {
set_fee(program_id, stake_pool, manager, FeeType::StakeDeposit(fee))
}
/// Creates a 'set fee' instruction for setting SOL referral fee
pub fn set_sol_referral_fee(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
fee: u8,
) -> Instruction {
set_fee(program_id, stake_pool, manager, FeeType::SolReferral(fee))
}
/// Creates a 'set fee' instruction for setting stake referral fee
pub fn set_stake_referral_fee(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
fee: u8,
) -> Instruction {
set_fee(program_id, stake_pool, manager, FeeType::StakeReferral(fee))
}
/// Creates a 'set staker' instruction.
@ -1047,3 +1210,79 @@ pub fn set_staker(
data: StakePoolInstruction::SetStaker.try_to_vec().unwrap(),
}
}
/// Creates a 'set sol deposit authority' instruction.
pub fn set_deposit_authority(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
new_sol_deposit_authority: Option<&Pubkey>,
for_stake_deposit: bool,
) -> Instruction {
let mut accounts = vec![
AccountMeta::new(*stake_pool, false),
AccountMeta::new_readonly(*manager, true),
];
if let Some(auth) = new_sol_deposit_authority {
accounts.push(AccountMeta::new_readonly(*auth, false))
}
Instruction {
program_id: *program_id,
accounts,
data: if for_stake_deposit {
StakePoolInstruction::SetDepositAuthority(DepositType::Stake)
.try_to_vec()
.unwrap()
} else {
StakePoolInstruction::SetDepositAuthority(DepositType::Sol)
.try_to_vec()
.unwrap()
},
}
}
/// Creates a 'set stake deposit authority' instruction.
pub fn set_stake_deposit_authority(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
new_stake_deposit_authority: Option<&Pubkey>,
) -> Instruction {
let mut accounts = vec![
AccountMeta::new(*stake_pool, false),
AccountMeta::new_readonly(*manager, true),
];
if let Some(auth) = new_stake_deposit_authority {
accounts.push(AccountMeta::new_readonly(*auth, false))
}
Instruction {
program_id: *program_id,
accounts,
data: StakePoolInstruction::SetDepositAuthority(DepositType::Stake)
.try_to_vec()
.unwrap(),
}
}
/// Creates a 'set stake deposit authority' instruction.
pub fn set_sol_deposit_authority(
program_id: &Pubkey,
stake_pool: &Pubkey,
manager: &Pubkey,
new_stake_deposit_authority: Option<&Pubkey>,
) -> Instruction {
let mut accounts = vec![
AccountMeta::new(*stake_pool, false),
AccountMeta::new_readonly(*manager, true),
];
if let Some(auth) = new_stake_deposit_authority {
accounts.push(AccountMeta::new_readonly(*auth, false))
}
Instruction {
program_id: *program_id,
accounts,
data: StakePoolInstruction::SetDepositAuthority(DepositType::Sol)
.try_to_vec()
.unwrap(),
}
}

View File

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

View File

@ -1,5 +1,6 @@
//! Program state processor
use crate::instruction::DepositType;
use {
crate::{
error::StakePoolError,
@ -7,11 +8,10 @@ use {
instruction::{PreferredValidatorType, StakePoolInstruction},
minimum_reserve_lamports, minimum_stake_lamports, stake_program,
state::{
AccountType, Fee, StakePool, StakeStatus, ValidatorList, ValidatorListHeader,
AccountType, Fee, FeeType, StakePool, StakeStatus, ValidatorList, ValidatorListHeader,
ValidatorStakeInfo,
},
AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MAX_WITHDRAWAL_FEE_INCREASE, MINIMUM_ACTIVE_STAKE,
TRANSIENT_STAKE_SEED, WITHDRAWAL_BASELINE_FEE,
AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MINIMUM_ACTIVE_STAKE, TRANSIENT_STAKE_SEED,
},
borsh::{BorshDeserialize, BorshSerialize},
num_traits::FromPrimitive,
@ -468,12 +468,24 @@ impl Processor {
invoke(&ix, &[source, destination, authority, token_program])
}
fn sol_transfer<'a>(
source: AccountInfo<'a>,
destination: AccountInfo<'a>,
system_program: AccountInfo<'a>,
amount: u64,
) -> Result<(), ProgramError> {
let ix = solana_program::system_instruction::transfer(source.key, destination.key, amount);
invoke(&ix, &[source, destination, system_program])
}
/// Processes `Initialize` instruction.
fn process_initialize(
program_id: &Pubkey,
accounts: &[AccountInfo],
fee: Fee,
withdrawal_fee: Fee,
stake_deposit_fee: Fee,
stake_referral_fee: u8,
max_validators: u32,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -538,7 +550,11 @@ impl Processor {
}
// Numerator should be smaller than or equal to denominator (fee <= 1)
if fee.numerator > fee.denominator {
if fee.numerator > fee.denominator
|| withdrawal_fee.numerator > withdrawal_fee.denominator
|| stake_deposit_fee.numerator > stake_deposit_fee.denominator
|| stake_referral_fee > 100u8
{
return Err(StakePoolError::FeeTooHigh.into());
}
@ -556,11 +572,11 @@ impl Processor {
return Err(StakePoolError::WrongAccountMint.into());
}
let deposit_authority = match next_account_info(account_info_iter) {
Ok(deposit_authority_info) => *deposit_authority_info.key,
let stake_deposit_authority = match next_account_info(account_info_iter) {
Ok(stake_deposit_authority_info) => *stake_deposit_authority_info.key,
Err(_) => find_deposit_authority_program_address(program_id, stake_pool_info.key).0,
};
let (withdraw_authority_key, withdraw_bump_seed) =
let (withdraw_authority_key, stake_withdraw_bump_seed) =
crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key);
let pool_mint = Mint::unpack_from_slice(&pool_mint_info.data.borrow())?;
@ -616,10 +632,6 @@ impl Processor {
msg!("Reserve stake account not in intialized state");
return Err(StakePoolError::WrongStakeState.into());
};
// Numerator should be smaller than or equal to denominator (fee <= 1)
if withdrawal_fee.numerator > withdrawal_fee.denominator {
return Err(StakePoolError::FeeTooHigh.into());
}
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
@ -627,8 +639,8 @@ impl Processor {
stake_pool.manager = *manager_info.key;
stake_pool.staker = *staker_info.key;
stake_pool.reserve_stake = *reserve_stake_info.key;
stake_pool.deposit_authority = deposit_authority;
stake_pool.withdraw_bump_seed = withdraw_bump_seed;
stake_pool.stake_deposit_authority = stake_deposit_authority;
stake_pool.stake_withdraw_bump_seed = stake_withdraw_bump_seed;
stake_pool.validator_list = *validator_list_info.key;
stake_pool.pool_mint = *pool_mint_info.key;
stake_pool.manager_fee_account = *manager_fee_info.key;
@ -639,9 +651,11 @@ impl Processor {
stake_pool.next_epoch_fee = None;
stake_pool.preferred_deposit_validator_vote_address = None;
stake_pool.preferred_withdraw_validator_vote_address = None;
stake_pool.deposit_fee = Fee::default();
stake_pool.stake_deposit_fee = stake_deposit_fee;
stake_pool.withdrawal_fee = withdrawal_fee;
stake_pool.next_withdrawal_fee = None;
stake_pool.stake_referral_fee = stake_referral_fee;
stake_pool.sol_deposit_authority = None;
stake_pool
.serialize(&mut *stake_pool_info.data.borrow_mut())
@ -798,7 +812,7 @@ impl Processor {
)?;
if meta.lockup != stake_program::Lockup::default() {
msg!("Validator stake account has a lockup");
msg!("Stake account has a lockup");
return Err(StakePoolError::WrongStakeState.into());
}
@ -813,20 +827,13 @@ impl Processor {
// Check amount of lamports
let stake_lamports = **stake_account_info.lamports.borrow();
let minimum_lamport_amount = minimum_stake_lamports(&meta);
if stake_lamports != minimum_lamport_amount {
if stake_lamports != minimum_lamport_amount
|| stake.delegation.stake != MINIMUM_ACTIVE_STAKE
{
msg!(
"Error: attempting to add stake with {} lamports, must have {} lamports",
"Error: attempting to add (stake: {}, delegation: {}), below minimum",
stake_lamports,
minimum_lamport_amount
);
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
}
if stake.delegation.stake != MINIMUM_ACTIVE_STAKE {
msg!(
"Error: attempting to add stake with delegation of {} lamports, must have {} lamports",
stake.delegation.stake,
MINIMUM_ACTIVE_STAKE
);
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
}
@ -970,7 +977,7 @@ impl Processor {
stake_account_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
new_stake_authority_info.key,
clock_info.clone(),
stake_program_info.clone(),
@ -1097,7 +1104,7 @@ impl Processor {
validator_stake_account_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
lamports,
transient_stake_account_info.clone(),
)?;
@ -1109,7 +1116,7 @@ impl Processor {
withdraw_authority_info.clone(),
stake_pool_info.key,
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
)?;
validator_stake_info.active_stake_lamports = validator_stake_info
@ -1249,7 +1256,7 @@ impl Processor {
reserve_stake_account_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
total_lamports,
transient_stake_account_info.clone(),
)?;
@ -1264,7 +1271,7 @@ impl Processor {
withdraw_authority_info.clone(),
stake_pool_info.key,
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
)?;
validator_stake_info.transient_stake_lamports = total_lamports;
@ -1427,7 +1434,7 @@ impl Processor {
transient_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
reserve_stake_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
@ -1453,7 +1460,7 @@ impl Processor {
transient_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
reserve_stake_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
@ -1479,7 +1486,7 @@ impl Processor {
transient_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
validator_stake_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
@ -1494,7 +1501,7 @@ impl Processor {
validator_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
reserve_stake_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
@ -1623,7 +1630,7 @@ impl Processor {
let reward_lamports = total_stake_lamports.saturating_sub(previous_lamports);
let fee = stake_pool
.calc_fee_amount(reward_lamports)
.calc_epoch_fee_amount(reward_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
if fee > 0 {
@ -1634,7 +1641,7 @@ impl Processor {
manager_fee_info.clone(),
withdraw_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
fee,
)?;
@ -1718,17 +1725,19 @@ impl Processor {
Ok(())
}
/// Processes [Deposit](enum.Instruction.html).
fn process_deposit(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
/// Processes [DepositStake](enum.Instruction.html).
fn process_deposit_stake(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_pool_info = next_account_info(account_info_iter)?;
let validator_list_info = next_account_info(account_info_iter)?;
let deposit_authority_info = next_account_info(account_info_iter)?;
let stake_deposit_authority_info = next_account_info(account_info_iter)?;
let withdraw_authority_info = next_account_info(account_info_iter)?;
let stake_info = next_account_info(account_info_iter)?;
let validator_stake_account_info = next_account_info(account_info_iter)?;
let reserve_stake_account_info = next_account_info(account_info_iter)?;
let dest_user_info = next_account_info(account_info_iter)?;
let dest_user_pool_info = next_account_info(account_info_iter)?;
let manager_fee_info = next_account_info(account_info_iter)?;
let referrer_fee_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
@ -1753,7 +1762,7 @@ impl Processor {
program_id,
stake_pool_info.key,
)?;
stake_pool.check_deposit_authority(deposit_authority_info.key)?;
stake_pool.check_stake_deposit_authority(stake_deposit_authority_info.key)?;
stake_pool.check_mint(pool_mint_info)?;
stake_pool.check_validator_list(validator_list_info)?;
stake_pool.check_reserve_stake(reserve_stake_account_info)?;
@ -1762,6 +1771,10 @@ impl Processor {
return Err(ProgramError::IncorrectProgramId);
}
if stake_pool.manager_fee_account != *manager_fee_info.key {
return Err(StakePoolError::InvalidFeeAccount.into());
}
if stake_pool.last_update_epoch < clock.epoch {
return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
}
@ -1808,13 +1821,13 @@ impl Processor {
msg!("Stake pre merge {}", validator_stake.delegation.stake);
let (deposit_authority_program_address, deposit_bump_seed) =
let (stake_deposit_authority_program_address, deposit_bump_seed) =
find_deposit_authority_program_address(program_id, stake_pool_info.key);
if *deposit_authority_info.key == deposit_authority_program_address {
if *stake_deposit_authority_info.key == stake_deposit_authority_program_address {
Self::stake_authorize_signed(
stake_pool_info.key,
stake_info.clone(),
deposit_authority_info.clone(),
stake_deposit_authority_info.clone(),
AUTHORITY_DEPOSIT,
deposit_bump_seed,
withdraw_authority_info.key,
@ -1824,7 +1837,7 @@ impl Processor {
} else {
Self::stake_authorize(
stake_info.clone(),
deposit_authority_info.clone(),
stake_deposit_authority_info.clone(),
withdraw_authority_info.key,
clock_info.clone(),
stake_program_info.clone(),
@ -1836,7 +1849,7 @@ impl Processor {
stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
validator_stake_account_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
@ -1854,24 +1867,71 @@ impl Processor {
.stake
.checked_sub(validator_stake.delegation.stake)
.ok_or(StakePoolError::CalculationFailure)?;
let new_pool_tokens = stake_pool
.calc_pool_tokens_for_deposit(all_deposit_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
let pool_tokens_stake_deposit_fee = stake_pool
.calc_pool_tokens_stake_deposit_fee(new_pool_tokens)
.ok_or(StakePoolError::CalculationFailure)?;
let pool_tokens_user = new_pool_tokens
.checked_sub(pool_tokens_stake_deposit_fee)
.ok_or(StakePoolError::CalculationFailure)?;
let pool_tokens_referral_fee = stake_pool
.calc_pool_tokens_stake_referral_fee(pool_tokens_stake_deposit_fee)
.ok_or(StakePoolError::CalculationFailure)?;
let pool_tokens_manager_deposit_fee = pool_tokens_stake_deposit_fee
.checked_sub(pool_tokens_referral_fee)
.ok_or(StakePoolError::CalculationFailure)?;
if pool_tokens_user + pool_tokens_manager_deposit_fee + pool_tokens_referral_fee
!= new_pool_tokens
{
return Err(StakePoolError::CalculationFailure.into());
}
if new_pool_tokens == 0 {
return Err(StakePoolError::DepositTooSmall.into());
}
if pool_tokens_user > 0 {
Self::token_mint_to(
stake_pool_info.key,
token_program_info.clone(),
pool_mint_info.clone(),
dest_user_info.clone(),
dest_user_pool_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
new_pool_tokens,
stake_pool.stake_withdraw_bump_seed,
pool_tokens_user,
)?;
}
if pool_tokens_manager_deposit_fee > 0 {
Self::token_mint_to(
stake_pool_info.key,
token_program_info.clone(),
pool_mint_info.clone(),
manager_fee_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
pool_tokens_manager_deposit_fee,
)?;
}
if pool_tokens_referral_fee > 0 {
Self::token_mint_to(
stake_pool_info.key,
token_program_info.clone(),
pool_mint_info.clone(),
referrer_fee_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
pool_tokens_referral_fee,
)?;
}
// withdraw additional lamports to the reserve
let additional_lamports = all_deposit_lamports
@ -1883,7 +1943,7 @@ impl Processor {
validator_stake_account_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
reserve_stake_account_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
@ -1911,8 +1971,146 @@ impl Processor {
Ok(())
}
/// Processes [Withdraw](enum.Instruction.html).
fn process_withdraw(
/// Processes [DepositStake](enum.Instruction.html).
fn process_deposit_sol(
program_id: &Pubkey,
accounts: &[AccountInfo],
deposit_lamports: u64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_pool_info = next_account_info(account_info_iter)?;
let withdraw_authority_info = next_account_info(account_info_iter)?;
let reserve_stake_account_info = next_account_info(account_info_iter)?;
let from_user_lamports_info = next_account_info(account_info_iter)?;
let dest_user_pool_info = next_account_info(account_info_iter)?;
let manager_fee_info = next_account_info(account_info_iter)?;
let referrer_fee_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
let system_program_info = next_account_info(account_info_iter)?;
let token_program_info = next_account_info(account_info_iter)?;
let sol_deposit_authority_info = next_account_info(account_info_iter).ok();
check_account_owner(stake_pool_info, program_id)?;
let mut stake_pool = try_from_slice_unchecked::<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,
accounts: &[AccountInfo],
pool_tokens: u64,
@ -1925,7 +2123,7 @@ impl Processor {
let stake_split_to = next_account_info(account_info_iter)?;
let user_stake_authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let burn_from_info = next_account_info(account_info_iter)?;
let burn_from_pool_info = next_account_info(account_info_iter)?;
let manager_fee_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
@ -1967,7 +2165,7 @@ impl Processor {
return Err(StakePoolError::InvalidState.into());
}
let pool_tokens_fee = if stake_pool.manager_fee_account == *burn_from_info.key {
let pool_tokens_fee = if stake_pool.manager_fee_account == *burn_from_pool_info.key {
0
} else {
stake_pool
@ -2079,7 +2277,7 @@ impl Processor {
Self::token_burn(
token_program_info.clone(),
burn_from_info.clone(),
burn_from_pool_info.clone(),
pool_mint_info.clone(),
user_transfer_authority_info.clone(),
pool_tokens_burnt,
@ -2090,7 +2288,7 @@ impl Processor {
stake_split_from.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
withdraw_lamports,
stake_split_to.clone(),
)?;
@ -2100,7 +2298,7 @@ impl Processor {
stake_split_to.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_pool.stake_withdraw_bump_seed,
user_stake_authority_info.key,
clock_info.clone(),
stake_program_info.clone(),
@ -2109,7 +2307,7 @@ impl Processor {
if pool_tokens_fee > 0 {
Self::token_transfer(
token_program_info.clone(),
burn_from_info.clone(),
burn_from_pool_info.clone(),
manager_fee_info.clone(),
user_transfer_authority_info.clone(),
pool_tokens_fee,
@ -2175,7 +2373,11 @@ impl Processor {
}
/// Processes [SetFee](enum.Instruction.html).
fn process_set_fee(program_id: &Pubkey, accounts: &[AccountInfo], fee: Fee) -> ProgramResult {
fn process_set_fee(
program_id: &Pubkey,
accounts: &[AccountInfo],
fee: FeeType,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_pool_info = next_account_info(account_info_iter)?;
let manager_info = next_account_info(account_info_iter)?;
@ -2190,21 +2392,14 @@ impl Processor {
stake_pool.check_manager(manager_info)?;
if stake_pool.last_update_epoch < clock.epoch {
if fee.can_only_change_next_epoch() && stake_pool.last_update_epoch < clock.epoch {
return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
}
// Numerator should be smaller than or equal to denominator (fee <= 1)
if fee.numerator > fee.denominator {
msg!(
"Fee greater than 100%, numerator {}, denominator {}",
fee.numerator,
fee.denominator
);
return Err(StakePoolError::FeeTooHigh.into());
}
fee.check_too_high()?;
fee.check_withdrawal(&stake_pool.withdrawal_fee)?;
stake_pool.next_epoch_fee = Some(fee);
stake_pool.update_fee(&fee);
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
Ok(())
}
@ -2232,76 +2427,34 @@ impl Processor {
Ok(())
}
/// Processes [SetWithdrawalFee](enum.Instruction.html).
fn process_set_withdrawal_fee(
/// Processes [SetStakeDepositAuthority/SetSolDepositAuthority](enum.Instruction.html).
fn process_set_deposit_authority(
program_id: &Pubkey,
accounts: &[AccountInfo],
fee: Fee,
deposit_type: DepositType,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_pool_info = next_account_info(account_info_iter)?;
let manager_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
let new_sol_deposit_authority = next_account_info(account_info_iter).ok().map(
|new_sol_deposit_authority_account_info| *new_sol_deposit_authority_account_info.key,
);
check_account_owner(stake_pool_info, program_id)?;
let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
if !stake_pool.is_valid() {
return Err(StakePoolError::InvalidState.into());
}
stake_pool.check_manager(manager_info)?;
if stake_pool.last_update_epoch < clock.epoch {
return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
}
// 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
match deposit_type {
DepositType::Stake => {
stake_pool.stake_deposit_authority = new_sol_deposit_authority.unwrap_or(
find_deposit_authority_program_address(program_id, stake_pool_info.key).0,
);
return Err(StakePoolError::FeeTooHigh.into());
}
// If the previous withdrawal fee was 0, we allow the fee to be set to a
// maximum of (WITHDRAWAL_BASELINE_FEE * MAX_WITHDRAWAL_FEE_INCREASE)
let (old_num, old_denom) = if stake_pool.withdrawal_fee.denominator == 0
|| stake_pool.withdrawal_fee.numerator == 0
{
(
WITHDRAWAL_BASELINE_FEE.numerator,
WITHDRAWAL_BASELINE_FEE.denominator,
)
} else {
(
stake_pool.withdrawal_fee.numerator,
stake_pool.withdrawal_fee.denominator,
)
};
// Check that new_fee / old_fee <= MAX_WITHDRAWAL_FEE_INCREASE
// Program fails if provided numerator or denominator is too large, resulting in overflow
if (old_num as u128)
.checked_mul(fee.denominator as u128)
.map(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.numerator as u128))
.ok_or(StakePoolError::CalculationFailure)?
< (fee.numerator as u128)
.checked_mul(old_denom as u128)
.map(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.denominator as u128))
.ok_or(StakePoolError::CalculationFailure)?
{
msg!(
"Fee increase exceeds maximum allowed, proposed increase factor ({} / {})",
fee.numerator * old_denom,
old_num * fee.denominator,
);
return Err(StakePoolError::FeeIncreaseTooHigh.into());
DepositType::Sol => stake_pool.sol_deposit_authority = new_sol_deposit_authority,
}
stake_pool.next_withdrawal_fee = Some(fee);
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
Ok(())
}
@ -2313,10 +2466,20 @@ impl Processor {
StakePoolInstruction::Initialize {
fee,
withdrawal_fee,
deposit_fee,
referral_fee,
max_validators,
} => {
msg!("Instruction: Initialize stake pool");
Self::process_initialize(program_id, accounts, fee, withdrawal_fee, max_validators)
Self::process_initialize(
program_id,
accounts,
fee,
withdrawal_fee,
deposit_fee,
referral_fee,
max_validators,
)
}
StakePoolInstruction::CreateValidatorStakeAccount => {
msg!("Instruction: CreateValidatorStakeAccount");
@ -2370,13 +2533,13 @@ impl Processor {
msg!("Instruction: CleanupRemovedValidatorEntries");
Self::process_cleanup_removed_validator_entries(program_id, accounts)
}
StakePoolInstruction::Deposit => {
msg!("Instruction: Deposit");
Self::process_deposit(program_id, accounts)
StakePoolInstruction::DepositStake => {
msg!("Instruction: DepositStake");
Self::process_deposit_stake(program_id, accounts)
}
StakePoolInstruction::Withdraw(amount) => {
msg!("Instruction: Withdraw");
Self::process_withdraw(program_id, accounts, amount)
StakePoolInstruction::WithdrawStake(amount) => {
msg!("Instruction: WithdrawStake");
Self::process_withdraw_stake(program_id, accounts, amount)
}
StakePoolInstruction::SetManager => {
msg!("Instruction: SetManager");
@ -2390,9 +2553,13 @@ impl Processor {
msg!("Instruction: SetStaker");
Self::process_set_staker(program_id, accounts)
}
StakePoolInstruction::SetWithdrawalFee { fee } => {
msg!("Instruction: SetWithdrawalFee");
Self::process_set_withdrawal_fee(program_id, accounts, fee)
StakePoolInstruction::DepositSol(lamports) => {
msg!("Instruction: DepositSol");
Self::process_deposit_sol(program_id, accounts, lamports)
}
StakePoolInstruction::SetDepositAuthority(deposit_type) => {
msg!("Instruction: SetDepositAuthority");
Self::process_set_deposit_authority(program_id, accounts, deposit_type)
}
}
}
@ -2436,6 +2603,8 @@ impl PrintProgramError for StakePoolError {
StakePoolError::FeeIncreaseTooHigh => msg!("Error: The fee cannot increase by a factor exceeding the stipulated ratio"),
StakePoolError::WithdrawalTooSmall => msg!("Error: Not enough pool tokens provided to withdraw 1-lamport stake"),
StakePoolError::DepositTooSmall => msg!("Error: Not enough lamports provided for deposit to result in one pool token"),
StakePoolError::InvalidStakeDepositAuthority => msg!("Error: Provided stake deposit authority does not match the program's"),
StakePoolError::InvalidSolDepositAuthority => msg!("Error: Provided sol deposit authority does not match the program's"),
}
}
}

View File

@ -1,7 +1,10 @@
//! State transition types
use {
crate::{big_vec::BigVec, error::StakePoolError, stake_program::Lockup},
crate::{
big_vec::BigVec, error::StakePoolError, stake_program::Lockup, MAX_WITHDRAWAL_FEE_INCREASE,
WITHDRAWAL_BASELINE_FEE,
},
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
num_derive::FromPrimitive,
num_traits::FromPrimitive,
@ -16,6 +19,7 @@ use {
},
spl_math::checked_ceil_div::CheckedCeilDiv,
std::convert::TryFrom,
std::{fmt, matches},
};
/// Enum representing the account type managed by the program
@ -49,7 +53,7 @@ pub struct StakePool {
/// distribution
pub staker: Pubkey,
/// Deposit authority
/// Stake deposit authority
///
/// If a depositor pubkey is specified on initialization, then deposits must be
/// signed by this authority. If no deposit authority is specified,
@ -58,11 +62,11 @@ pub struct StakePool {
/// &[&stake_pool_address.to_bytes()[..32], b"deposit"],
/// program_id,
/// )`
pub deposit_authority: Pubkey,
pub stake_deposit_authority: Pubkey,
/// Withdrawal authority bump seed
/// Stake withdrawal authority bump seed
/// for `create_program_address(&[state::StakePool account, "withdrawal"])`
pub withdraw_bump_seed: u8,
pub stake_withdraw_bump_seed: u8,
/// Validator stake list storage account
pub validator_list: Pubkey,
@ -105,8 +109,8 @@ pub struct StakePool {
/// Preferred withdraw validator vote account pubkey
pub preferred_withdraw_validator_vote_address: Option<Pubkey>,
/// Fee assessed on deposits
pub deposit_fee: Fee,
/// Fee assessed on stake deposits
pub stake_deposit_fee: Fee,
/// Fee assessed on withdrawals
pub withdrawal_fee: Fee,
@ -114,14 +118,28 @@ pub struct StakePool {
/// Future withdrawal fee, to be set for the following epoch
pub next_withdrawal_fee: Option<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.
/// i.e. `deposit_fee`% is collected as deposit fees for every deposit
/// and `referral_fee`% of the collected deposit fees is paid out to the referrer
pub referral_fee: u8,
/// i.e. `stake_deposit_fee`% of stake deposited is collected as deposit fees for every deposit
/// and `stake_referral_fee`% of the collected stake deposit fees is paid out to the referrer
pub stake_referral_fee: u8,
/// Toggles whether the `DepositSol` instruction requires a signature from
/// the `deposit_authority`
pub sol_deposit_authority: Option<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 {
/// 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> {
if self.total_stake_lamports == 0 || self.pool_token_supply == 0 {
return Some(stake_lamports);
@ -135,6 +153,7 @@ impl StakePool {
}
/// calculate lamports amount on withdrawal
#[inline]
pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> {
// `checked_ceil_div` returns `None` for a 0 quotient result, but in this
// case, a return of 0 is valid for small amounts of pool tokens. So
@ -150,15 +169,51 @@ impl StakePool {
}
/// calculate pool tokens to be deducted as withdrawal fees
#[inline]
pub fn calc_pool_tokens_withdrawal_fee(&self, pool_tokens: u64) -> Option<u64> {
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
///
/// This function assumes that `reward_lamports` has not already been added
/// to the stake pool's `total_stake_lamports`
pub fn calc_fee_amount(&self, reward_lamports: u64) -> Option<u64> {
#[inline]
pub fn calc_epoch_fee_amount(&self, reward_lamports: u64) -> Option<u64> {
if reward_lamports == 0 {
return Some(0);
}
@ -178,7 +233,7 @@ impl StakePool {
}
/// Checks that the withdraw or deposit authority is valid
fn check_authority(
fn check_program_derived_authority(
authority_address: &Pubkey,
program_id: &Pubkey,
stake_pool_address: &Pubkey,
@ -207,33 +262,55 @@ impl StakePool {
}
/// Checks that the withdraw authority is valid
#[inline]
pub(crate) fn check_authority_withdraw(
&self,
withdraw_authority: &Pubkey,
program_id: &Pubkey,
stake_pool_address: &Pubkey,
) -> Result<(), ProgramError> {
Self::check_authority(
Self::check_program_derived_authority(
withdraw_authority,
program_id,
stake_pool_address,
crate::AUTHORITY_WITHDRAW,
self.withdraw_bump_seed,
self.stake_withdraw_bump_seed,
)
}
/// Checks that the deposit authority is valid
pub(crate) fn check_deposit_authority(
#[inline]
pub(crate) fn check_stake_deposit_authority(
&self,
deposit_authority: &Pubkey,
stake_deposit_authority: &Pubkey,
) -> Result<(), ProgramError> {
if self.deposit_authority == *deposit_authority {
if self.stake_deposit_authority == *stake_deposit_authority {
Ok(())
} else {
Err(StakePoolError::InvalidProgramAddress.into())
Err(StakePoolError::InvalidStakeDepositAuthority.into())
}
}
/// Checks that the deposit authority is valid
/// Does nothing if `sol_deposit_authority` is currently not set
#[inline]
pub(crate) fn check_sol_deposit_authority(
&self,
sol_deposit_authority: &AccountInfo,
) -> Result<(), ProgramError> {
if let Some(auth) = self.sol_deposit_authority {
if auth != *sol_deposit_authority.key {
return Err(StakePoolError::InvalidSolDepositAuthority.into());
}
if !sol_deposit_authority.is_signer {
msg!("SOL Deposit authority signature missing");
return Err(StakePoolError::SignatureMissing.into());
}
}
Ok(())
}
/// Check staker validity and signature
#[inline]
pub(crate) fn check_mint(&self, mint_info: &AccountInfo) -> Result<(), ProgramError> {
if *mint_info.key != self.pool_mint {
Err(StakePoolError::WrongPoolMint.into())
@ -319,6 +396,18 @@ impl StakePool {
pub fn is_uninitialized(&self) -> bool {
self.account_type == AccountType::Uninitialized
}
/// Updates one of the StakePool's fees.
pub fn update_fee(&mut self, fee: &FeeType) {
match fee {
FeeType::SolReferral(new_fee) => self.sol_referral_fee = *new_fee,
FeeType::StakeReferral(new_fee) => self.stake_referral_fee = *new_fee,
FeeType::Epoch(new_fee) => self.next_epoch_fee = Some(*new_fee),
FeeType::Withdrawal(new_fee) => self.next_withdrawal_fee = Some(*new_fee),
FeeType::SolDeposit(new_fee) => self.sol_deposit_fee = *new_fee,
FeeType::StakeDeposit(new_fee) => self.stake_deposit_fee = *new_fee,
}
}
}
/// Storage list for all validator stake accounts in the pool.
@ -579,6 +668,96 @@ impl Fee {
}
}
impl fmt::Display for Fee {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.numerator, self.denominator)
}
}
/// The type of fees that can be set on the stake pool
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum FeeType {
/// Referral fees for SOL deposits
SolReferral(u8),
/// Referral fees for stake deposits
StakeReferral(u8),
/// Management fee paid per epoch
Epoch(Fee),
/// Withdrawal fee
Withdrawal(Fee),
/// Deposit fee for SOL deposits
SolDeposit(Fee),
/// Deposit fee for stake deposits
StakeDeposit(Fee),
}
impl FeeType {
/// Checks if the provided fee is too high, returning an error if so
pub fn check_too_high(&self) -> Result<(), StakePoolError> {
let too_high = match self {
Self::SolReferral(pct) => *pct > 100u8,
Self::StakeReferral(pct) => *pct > 100u8,
Self::Epoch(fee) => fee.numerator > fee.denominator,
Self::Withdrawal(fee) => fee.numerator > fee.denominator,
Self::SolDeposit(fee) => fee.numerator > fee.denominator,
Self::StakeDeposit(fee) => fee.numerator > fee.denominator,
};
if too_high {
msg!("Fee greater than 100%: {:?}", self);
return Err(StakePoolError::FeeTooHigh);
}
Ok(())
}
/// Withdrawal fees have some additional restrictions,
/// this fn checks if those are met, returning an error if not.
/// Does nothing and returns Ok if fee type is not withdrawal
pub fn check_withdrawal(&self, old_withdrawal_fee: &Fee) -> Result<(), StakePoolError> {
let fee = match self {
Self::Withdrawal(fee) => fee,
_ => return Ok(()),
};
// If the previous withdrawal fee was 0, we allow the fee to be set to a
// maximum of (WITHDRAWAL_BASELINE_FEE * MAX_WITHDRAWAL_FEE_INCREASE)
let (old_num, old_denom) =
if old_withdrawal_fee.denominator == 0 || old_withdrawal_fee.numerator == 0 {
(
WITHDRAWAL_BASELINE_FEE.numerator,
WITHDRAWAL_BASELINE_FEE.denominator,
)
} else {
(old_withdrawal_fee.numerator, old_withdrawal_fee.denominator)
};
// Check that new_fee / old_fee <= MAX_WITHDRAWAL_FEE_INCREASE
// Program fails if provided numerator or denominator is too large, resulting in overflow
if (old_num as u128)
.checked_mul(fee.denominator as u128)
.map(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.numerator as u128))
.ok_or(StakePoolError::CalculationFailure)?
< (fee.numerator as u128)
.checked_mul(old_denom as u128)
.map(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.denominator as u128))
.ok_or(StakePoolError::CalculationFailure)?
{
msg!(
"Fee increase exceeds maximum allowed, proposed increase factor ({} / {})",
fee.numerator * old_denom,
old_num * fee.denominator,
);
return Err(StakePoolError::FeeIncreaseTooHigh);
}
Ok(())
}
/// Returns if the contained fee can only be updated earliest on the next epoch
#[inline]
pub fn can_only_change_next_epoch(&self) -> bool {
matches!(self, Self::Withdrawal(_) | Self::Epoch(_))
}
}
#[cfg(test)]
mod test {
use {
@ -779,7 +958,7 @@ mod test {
..StakePool::default()
};
let reward_lamports = 10 * LAMPORTS_PER_SOL;
let pool_token_fee = stake_pool.calc_fee_amount(reward_lamports).unwrap();
let pool_token_fee = stake_pool.calc_epoch_fee_amount(reward_lamports).unwrap();
stake_pool.total_stake_lamports += reward_lamports;
stake_pool.pool_token_supply += pool_token_fee;
@ -815,7 +994,7 @@ mod test {
..StakePool::default()
};
let rewards = 10;
let fee = stake_pool.calc_fee_amount(rewards).unwrap();
let fee = stake_pool.calc_epoch_fee_amount(rewards).unwrap();
assert_eq!(fee, rewards);
}
@ -832,7 +1011,7 @@ mod test {
fee,
..StakePool::default()
};
let pool_token_fee = stake_pool.calc_fee_amount(reward_lamports).unwrap();
let pool_token_fee = stake_pool.calc_epoch_fee_amount(reward_lamports).unwrap();
stake_pool.total_stake_lamports += reward_lamports;
stake_pool.pool_token_supply += pool_token_fee;

View File

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

View File

@ -212,7 +212,9 @@ async fn success() {
// Check minted tokens
let user_token_balance =
get_token_balance(&mut context.banks_client, &pool_token_account).await;
assert_eq!(user_token_balance, tokens_issued);
let tokens_issued_user =
tokens_issued - stake_pool_accounts.calculate_deposit_fee(tokens_issued);
assert_eq!(user_token_balance, tokens_issued_user);
// Check balances in validator stake account list storage
let validator_list = get_account(
@ -272,12 +274,14 @@ async fn fail_with_wrong_stake_program_id() {
let accounts = vec![
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
AccountMeta::new_readonly(stake_pool_accounts.deposit_authority, false),
AccountMeta::new_readonly(stake_pool_accounts.stake_deposit_authority, false),
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
AccountMeta::new(deposit_stake, false),
AccountMeta::new(validator_stake_account.stake_account, false),
AccountMeta::new(stake_pool_accounts.reserve_stake.pubkey(), false),
AccountMeta::new(pool_token_account, false),
AccountMeta::new(stake_pool_accounts.pool_fee_account.pubkey(), false),
AccountMeta::new(stake_pool_accounts.pool_fee_account.pubkey(), false),
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
@ -287,7 +291,7 @@ async fn fail_with_wrong_stake_program_id() {
let instruction = Instruction {
program_id: id(),
accounts,
data: instruction::StakePoolInstruction::Deposit
data: instruction::StakePoolInstruction::DepositStake
.try_to_vec()
.unwrap(),
};
@ -325,7 +329,7 @@ async fn fail_with_wrong_token_program_id() {
let wrong_token_program = Keypair::new();
let mut transaction = Transaction::new_with_payer(
&instruction::deposit(
&instruction::deposit_stake(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.validator_list.pubkey(),
@ -335,6 +339,8 @@ async fn fail_with_wrong_token_program_id() {
&validator_stake_account.stake_account,
&stake_pool_accounts.reserve_stake.pubkey(),
&pool_token_account,
&stake_pool_accounts.pool_fee_account.pubkey(),
&stake_pool_accounts.pool_fee_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&wrong_token_program.pubkey(),
),
@ -567,7 +573,9 @@ async fn fail_with_wrong_mint_for_receiver_acc() {
let program_error = token_error::TokenError::MintMismatch as u32;
assert_eq!(error_index, program_error);
}
_ => panic!("Wrong error occurs while try to deposit with wrong mint fro receiver account"),
_ => {
panic!("Wrong error occurs while try to deposit with wrong mint from receiver account")
}
}
}
@ -578,10 +586,11 @@ async fn fail_with_uninitialized_validator_list() {} // TODO
async fn fail_with_out_of_dated_pool_balances() {} // TODO
#[tokio::test]
async fn success_with_deposit_authority() {
async fn success_with_stake_deposit_authority() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let deposit_authority = Keypair::new();
let stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority);
let stake_deposit_authority = Keypair::new();
let stake_pool_accounts =
StakePoolAccounts::new_with_stake_deposit_authority(stake_deposit_authority);
stake_pool_accounts
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
.await
@ -659,10 +668,11 @@ async fn success_with_deposit_authority() {
}
#[tokio::test]
async fn fail_without_deposit_authority_signature() {
async fn fail_without_stake_deposit_authority_signature() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let deposit_authority = Keypair::new();
let mut stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority);
let stake_deposit_authority = Keypair::new();
let mut stake_pool_accounts =
StakePoolAccounts::new_with_stake_deposit_authority(stake_deposit_authority);
stake_pool_accounts
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
.await
@ -726,8 +736,8 @@ async fn fail_without_deposit_authority_signature() {
.unwrap();
let wrong_depositor = Keypair::new();
stake_pool_accounts.deposit_authority = wrong_depositor.pubkey();
stake_pool_accounts.deposit_authority_keypair = Some(wrong_depositor);
stake_pool_accounts.stake_deposit_authority = wrong_depositor.pubkey();
stake_pool_accounts.stake_deposit_authority_keypair = Some(wrong_depositor);
let error = stake_pool_accounts
.deposit_stake(
@ -747,7 +757,7 @@ async fn fail_without_deposit_authority_signature() {
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
assert_eq!(
error_index,
error::StakePoolError::InvalidProgramAddress as u32
error::StakePoolError::InvalidStakeDepositAuthority as u32
);
}
_ => panic!("Wrong error occurs while try to make a deposit with wrong stake program ID"),
@ -843,3 +853,113 @@ async fn fail_with_wrong_preferred_deposit() {
_ => panic!("Wrong error occurs while try to make a deposit with wrong stake program ID"),
}
}
#[tokio::test]
async fn success_with_referral_fee() {
let (
mut context,
stake_pool_accounts,
validator_stake_account,
user,
deposit_stake,
pool_token_account,
stake_lamports,
) = setup().await;
let referrer = Keypair::new();
let referrer_token_account = Keypair::new();
create_token_account(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&referrer_token_account,
&stake_pool_accounts.pool_mint.pubkey(),
&referrer.pubkey(),
)
.await
.unwrap();
let referrer_balance_pre =
get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;
let mut transaction = Transaction::new_with_payer(
&instruction::deposit_stake(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.validator_list.pubkey(),
&stake_pool_accounts.withdraw_authority,
&deposit_stake,
&user.pubkey(),
&validator_stake_account.stake_account,
&stake_pool_accounts.reserve_stake.pubkey(),
&pool_token_account,
&stake_pool_accounts.pool_fee_account.pubkey(),
&referrer_token_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&spl_token::id(),
),
Some(&context.payer.pubkey()),
);
transaction.sign(&[&context.payer, &user], context.last_blockhash);
context
.banks_client
.process_transaction(transaction)
.await
.unwrap();
let referrer_balance_post =
get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;
let referral_fee = stake_pool_accounts
.calculate_referral_fee(stake_pool_accounts.calculate_deposit_fee(stake_lamports));
assert!(referral_fee > 0);
assert_eq!(referrer_balance_pre + referral_fee, referrer_balance_post);
}
#[tokio::test]
async fn fail_with_invalid_referrer() {
let (
mut context,
stake_pool_accounts,
validator_stake_account,
user,
deposit_stake,
pool_token_account,
_stake_lamports,
) = setup().await;
let invalid_token_account = Keypair::new();
let mut transaction = Transaction::new_with_payer(
&instruction::deposit_stake(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.validator_list.pubkey(),
&stake_pool_accounts.withdraw_authority,
&deposit_stake,
&user.pubkey(),
&validator_stake_account.stake_account,
&stake_pool_accounts.reserve_stake.pubkey(),
&pool_token_account,
&stake_pool_accounts.pool_fee_account.pubkey(),
&invalid_token_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&spl_token::id(),
),
Some(&context.payer.pubkey()),
);
transaction.sign(&[&context.payer, &user], context.last_blockhash);
let transaction_error = context
.banks_client
.process_transaction(transaction)
.await
.err()
.unwrap()
.unwrap();
match transaction_error {
TransactionError::InstructionError(_, InstructionError::InvalidAccountData) => (),
_ => panic!(
"Wrong error occurs while try to make a deposit with an invalid referrer account"
),
}
}

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

View File

@ -32,7 +32,7 @@ use {
spl_token::state::{Account as SplAccount, AccountState as SplAccountState, Mint},
};
const HUGE_POOL_SIZE: u32 = 4_000;
const HUGE_POOL_SIZE: u32 = 3_950;
const ACCOUNT_RENT_EXEMPTION: u64 = 1_000_000_000; // go with something big to be safe
const STAKE_AMOUNT: u64 = 200_000_000_000;
const STAKE_ACCOUNT_RENT_EXEMPTION: u64 = 2_282_880;
@ -56,15 +56,15 @@ async fn setup(
stake_pool_accounts.max_validators = max_validators;
let stake_pool_pubkey = stake_pool_accounts.stake_pool.pubkey();
let (_, withdraw_bump_seed) =
let (_, stake_withdraw_bump_seed) =
find_withdraw_authority_program_address(&id(), &stake_pool_pubkey);
let mut stake_pool = StakePool {
account_type: AccountType::StakePool,
manager: stake_pool_accounts.manager.pubkey(),
staker: stake_pool_accounts.staker.pubkey(),
deposit_authority: stake_pool_accounts.deposit_authority,
withdraw_bump_seed,
stake_deposit_authority: stake_pool_accounts.stake_deposit_authority,
stake_withdraw_bump_seed,
validator_list: stake_pool_accounts.validator_list.pubkey(),
reserve_stake: stake_pool_accounts.reserve_stake.pubkey(),
pool_mint: stake_pool_accounts.pool_mint.pubkey(),
@ -78,10 +78,13 @@ async fn setup(
next_epoch_fee: None,
preferred_deposit_validator_vote_address: None,
preferred_withdraw_validator_vote_address: None,
deposit_fee: Fee::default(),
stake_deposit_fee: Fee::default(),
sol_deposit_fee: Fee::default(),
withdrawal_fee: Fee::default(),
next_withdrawal_fee: None,
referral_fee: 0,
stake_referral_fee: 0,
sol_referral_fee: 0,
sol_deposit_authority: None,
};
let mut validator_list = ValidatorList::new(max_validators);
@ -592,7 +595,7 @@ async fn add_validator_to_pool() {
increase_amount,
)
.await;
assert!(error.is_none());
assert!(error.is_none(), "{:?}", error);
let validator_list = get_account(
&mut context.banks_client,
@ -655,7 +658,7 @@ async fn set_preferred() {
}
#[tokio::test]
async fn deposit() {
async fn deposit_stake() {
let (mut context, stake_pool_accounts, _, vote_pubkey, user, stake_pubkey, pool_account_pubkey) =
setup(HUGE_POOL_SIZE, HUGE_POOL_SIZE, STAKE_AMOUNT).await;

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@ async fn setup() -> (
)
.await;
let deposit_info = simple_deposit(
let deposit_info = simple_deposit_stake(
&mut banks_client,
&payer,
&recent_blockhash,
@ -274,7 +274,7 @@ async fn fail_with_wrong_stake_program() {
let instruction = Instruction {
program_id: id(),
accounts,
data: instruction::StakePoolInstruction::Withdraw(tokens_to_burn)
data: instruction::StakePoolInstruction::WithdrawStake(tokens_to_burn)
.try_to_vec()
.unwrap(),
};
@ -361,7 +361,7 @@ async fn fail_with_wrong_token_program_id() {
let wrong_token_program = Keypair::new();
let transaction = Transaction::new_signed_with_payer(
&[instruction::withdraw(
&[instruction::withdraw_stake(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.validator_list.pubkey(),
@ -475,6 +475,67 @@ async fn fail_with_unknown_validator() {
)
.await;
let user_pool_account = Keypair::new();
let user = Keypair::new();
create_token_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_pool_account,
&stake_pool_accounts.pool_mint.pubkey(),
&user.pubkey(),
)
.await
.unwrap();
let user = Keypair::new();
// make stake account
let user_stake = Keypair::new();
let lockup = stake_program::Lockup::default();
let authorized = stake_program::Authorized {
staker: stake_pool_accounts.stake_deposit_authority,
withdrawer: stake_pool_accounts.stake_deposit_authority,
};
create_independent_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake,
&authorized,
&lockup,
TEST_STAKE_AMOUNT,
)
.await;
// make pool token account
let user_pool_account = Keypair::new();
create_token_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_pool_account,
&stake_pool_accounts.pool_mint.pubkey(),
&user.pubkey(),
)
.await
.unwrap();
let user_pool_account = user_pool_account.pubkey();
let pool_tokens = get_token_balance(&mut banks_client, &user_pool_account).await;
let tokens_to_burn = pool_tokens / 4;
// Delegate tokens for burning
delegate_tokens(
&mut banks_client,
&payer,
&recent_blockhash,
&user_pool_account,
&user,
&user_transfer_authority.pubkey(),
tokens_to_burn,
)
.await;
let new_authority = Pubkey::new_unique();
let transaction_error = stake_pool_accounts
.withdraw_stake(
@ -585,7 +646,7 @@ async fn fail_without_token_approval() {
)
.await;
let deposit_info = simple_deposit(
let deposit_info = simple_deposit_stake(
&mut banks_client,
&payer,
&recent_blockhash,
@ -656,7 +717,7 @@ async fn fail_with_low_delegation() {
)
.await;
let deposit_info = simple_deposit(
let deposit_info = simple_deposit_stake(
&mut banks_client,
&payer,
&recent_blockhash,
@ -796,7 +857,7 @@ async fn success_with_reserve() {
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let deposit_info = simple_deposit(
let deposit_info = simple_deposit_stake(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
@ -930,10 +991,12 @@ async fn success_with_reserve() {
assert!(error.is_none());
// first and only deposit, lamports:pool 1:1
let tokens_deposit_fee =
stake_pool_accounts.calculate_deposit_fee(deposit_info.stake_lamports + stake_rent);
let tokens_withdrawal_fee =
stake_pool_accounts.calculate_withdrawal_fee(deposit_info.pool_tokens);
assert_eq!(
deposit_info.stake_lamports + stake_rent,
deposit_info.stake_lamports + stake_rent - tokens_deposit_fee,
deposit_info.pool_tokens,
);
@ -954,8 +1017,12 @@ async fn success_with_reserve() {
let stake_state =
deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap();
let meta = stake_state.meta().unwrap();
// TODO: these numbers dont add up even with +tokens_deposit_fee
assert_eq!(
initial_reserve_lamports + meta.rent_exempt_reserve + tokens_withdrawal_fee,
initial_reserve_lamports
+ meta.rent_exempt_reserve
+ tokens_withdrawal_fee
+ tokens_deposit_fee,
reserve_stake_account.lamports
);
@ -964,7 +1031,9 @@ async fn success_with_reserve() {
get_account(&mut context.banks_client, &withdraw_destination.pubkey()).await;
assert_eq!(
user_stake_recipient_account.lamports,
initial_stake_lamports + deposit_info.stake_lamports + stake_rent - tokens_withdrawal_fee
initial_stake_lamports + deposit_info.stake_lamports + stake_rent
- tokens_withdrawal_fee
- tokens_deposit_fee
);
}
@ -1059,7 +1128,7 @@ async fn fail_with_wrong_preferred_withdraw() {
assert!(error.is_none());
// deposit into preferred, then fail
let _preferred_deposit = simple_deposit(
let _preferred_deposit = simple_deposit_stake(
&mut banks_client,
&payer,
&recent_blockhash,
@ -1152,7 +1221,7 @@ async fn success_withdraw_from_transient() {
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let deposit_info = simple_deposit(
let deposit_info = simple_deposit_stake(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,