Skip to content

Commit 183cb6c

Browse files
authored
program: Updates for better 1 SOL minimum delegation support (#756)
* deps: Bump to all newest stuff * Use newest stake program and fix post-update * program: Update for larger minimum required amount in accounts * Remove copy-pasta comment * Reapply cargo audit fix
1 parent b36cda4 commit 183cb6c

8 files changed

Lines changed: 2489 additions & 1284 deletions

File tree

Cargo.lock

Lines changed: 2318 additions & 1257 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ check-cfg = [
1717
]
1818

1919
[workspace.metadata.cli]
20-
solana = "3.1.14"
20+
solana = "4.0.3"

program/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ solana-account = { version = "4.3.1", features = ["bincode"] }
5454
solana-compute-budget-interface = "3.0.0"
5555
solana-native-token = "3.0.0"
5656
solana-program = "4.0.0"
57-
solana-program-test = { version = "3.1.8", features = ["agave-unstable-api"] }
58-
solana-sdk = "3.0.0"
57+
solana-program-test = { version = "4.2.0-rc.1", features = ["agave-unstable-api"] }
58+
solana-sdk = "4.0.0"
5959
solana-vote-interface = { version = "6.0.0", features = ["bincode"] }
6060
spl-token-interface = "3.0.0"
6161
test-case = "3.3"

program/src/processor.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2895,18 +2895,24 @@ impl Processor {
28952895
let lamports_per_pool_token = stake_pool
28962896
.get_lamports_per_pool_token()
28972897
.ok_or(StakePoolError::CalculationFailure)?;
2898-
let minimum_lamports_with_tolerance =
2899-
required_lamports.saturating_add(lamports_per_pool_token);
29002898

2901-
let has_active_stake = validator_list
2899+
// Since this instruction performs a stake split on an active stake, the
2900+
// source stake needs to have at least twice the minimum delegation
2901+
// amount, so that both accounts have at least the minimum delegation
2902+
// afterwards.
2903+
let minimum_lamports_with_tolerance = required_lamports
2904+
.saturating_add(stake_minimum_delegation)
2905+
.saturating_add(lamports_per_pool_token);
2906+
2907+
let has_withdrawable_active_stake = validator_list
29022908
.find::<ValidatorStakeInfo, _>(|x| {
29032909
ValidatorStakeInfo::active_lamports_greater_than(
29042910
x,
29052911
&minimum_lamports_with_tolerance,
29062912
) && ValidatorStakeInfo::is_active(x)
29072913
})
29082914
.is_some();
2909-
let has_transient_stake = validator_list
2915+
let has_withdrawable_transient_stake = validator_list
29102916
.find::<ValidatorStakeInfo, _>(|x| {
29112917
ValidatorStakeInfo::transient_lamports_greater_than(
29122918
x,
@@ -2917,7 +2923,7 @@ impl Processor {
29172923

29182924
let validator_list_item_info = if *stake_split_from.key == stake_pool.reserve_stake {
29192925
// check that the validator stake accounts have no withdrawable stake
2920-
if has_transient_stake || has_active_stake {
2926+
if has_withdrawable_transient_stake || has_withdrawable_active_stake {
29212927
msg!("Error withdrawing from reserve: validator stake accounts have lamports available, please use those first.");
29222928
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
29232929
}
@@ -2952,11 +2958,9 @@ impl Processor {
29522958
ValidatorStakeInfo::memcmp_pubkey(x, &preferred_withdraw_validator)
29532959
})
29542960
{
2955-
let available_lamports =
2956-
u64::from(preferred_validator_info.active_stake_lamports)
2957-
.saturating_sub(minimum_lamports_with_tolerance);
29582961
if preferred_withdraw_validator != vote_account_address
2959-
&& available_lamports > 0
2962+
&& u64::from(preferred_validator_info.active_stake_lamports)
2963+
>= minimum_lamports_with_tolerance
29602964
{
29612965
msg!("Validator vote address {} is preferred for withdrawals, it currently has {} lamports available. Please withdraw those before using other validator stake accounts.", preferred_withdraw_validator, u64::from(preferred_validator_info.active_stake_lamports));
29622966
return Err(StakePoolError::IncorrectWithdrawVoteAddress.into());
@@ -2972,7 +2976,7 @@ impl Processor {
29722976
})
29732977
.ok_or(StakePoolError::ValidatorNotFound)?;
29742978

2975-
let withdraw_source = if has_active_stake {
2979+
let withdraw_source = if has_withdrawable_active_stake {
29762980
// if there's any active stake, we must withdraw from an active
29772981
// stake account
29782982
check_validator_stake_address(
@@ -2983,7 +2987,7 @@ impl Processor {
29832987
NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
29842988
)?;
29852989
StakeWithdrawSource::Active
2986-
} else if has_transient_stake
2990+
} else if has_withdrawable_transient_stake
29872991
|| validator_stake_info.transient_stake_lamports != 0.into()
29882992
{
29892993
// if there's any transient stake, we must withdraw from there
1.27 KB
Binary file not shown.

program/tests/helpers/mod.rs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#![allow(dead_code)]
22

33
use {
4-
agave_feature_set::stake_raise_minimum_delegation_to_1_sol,
54
borsh::BorshDeserialize,
65
solana_compute_budget_interface::ComputeBudgetInstruction,
76
solana_program::{
@@ -55,17 +54,15 @@ const ACCOUNT_RENT_EXEMPTION: u64 = 1_000_000_000; // go with something big to b
5554

5655
pub fn program_test() -> ProgramTest {
5756
let mut program_test = ProgramTest::new("spl_stake_pool", id(), processor!(Processor::process));
58-
program_test.add_upgradeable_program_to_genesis("solana_stake_program", &stake::program::id());
59-
program_test.deactivate_feature(stake_raise_minimum_delegation_to_1_sol::id());
57+
program_test.add_program("solana_stake_program", stake::program::id(), None);
6058
program_test
6159
}
6260

6361
pub fn program_test_with_metadata_program() -> ProgramTest {
6462
let mut program_test = ProgramTest::default();
65-
program_test.add_upgradeable_program_to_genesis("solana_stake_program", &stake::program::id());
66-
program_test.deactivate_feature(stake_raise_minimum_delegation_to_1_sol::id());
6763
program_test.add_program("spl_stake_pool", id(), processor!(Processor::process));
6864
program_test.add_program("mpl_token_metadata", inline_mpl_token_metadata::id(), None);
65+
program_test.add_program("solana_stake_program", stake::program::id(), None);
6966
program_test
7067
}
7168

@@ -924,6 +921,16 @@ impl StakePoolAccounts {
924921
}
925922
}
926923

924+
pub fn new_without_fees() -> Self {
925+
Self {
926+
epoch_fee: state::Fee::default(),
927+
withdrawal_fee: state::Fee::default(),
928+
deposit_fee: state::Fee::default(),
929+
sol_deposit_fee: state::Fee::default(),
930+
..Default::default()
931+
}
932+
}
933+
927934
pub fn calculate_fee(&self, amount: u64) -> u64 {
928935
(amount * self.epoch_fee.numerator).div_ceil(self.epoch_fee.denominator)
929936
}
@@ -2609,20 +2616,18 @@ pub fn add_token_account(
26092616
program_test.add_account(*account_key, fee_account);
26102617
}
26112618

2612-
pub async fn setup_for_withdraw(
2613-
token_program_id: Pubkey,
2619+
pub async fn setup_for_withdraw_with_accounts(
2620+
stake_pool_accounts: &StakePoolAccounts,
26142621
reserve_lamports: u64,
26152622
) -> (
26162623
ProgramTestContext,
2617-
StakePoolAccounts,
26182624
ValidatorStakeAccount,
26192625
DepositStakeAccount,
26202626
Keypair,
26212627
Keypair,
26222628
u64,
26232629
) {
26242630
let mut context = program_test().start_with_context().await;
2625-
let stake_pool_accounts = StakePoolAccounts::new_with_token_program(token_program_id);
26262631
stake_pool_accounts
26272632
.initialize_stake_pool(
26282633
&mut context.banks_client,
@@ -2637,7 +2642,7 @@ pub async fn setup_for_withdraw(
26372642
&mut context.banks_client,
26382643
&context.payer,
26392644
&context.last_blockhash,
2640-
&stake_pool_accounts,
2645+
stake_pool_accounts,
26412646
None,
26422647
)
26432648
.await;
@@ -2653,7 +2658,7 @@ pub async fn setup_for_withdraw(
26532658
&mut context.banks_client,
26542659
&context.payer,
26552660
&context.last_blockhash,
2656-
&stake_pool_accounts,
2661+
stake_pool_accounts,
26572662
&validator_stake_account,
26582663
current_minimum_delegation * 3,
26592664
)
@@ -2686,6 +2691,38 @@ pub async fn setup_for_withdraw(
26862691
)
26872692
.await;
26882693

2694+
(
2695+
context,
2696+
validator_stake_account,
2697+
deposit_info,
2698+
user_transfer_authority,
2699+
user_stake_recipient,
2700+
tokens_to_withdraw,
2701+
)
2702+
}
2703+
2704+
pub async fn setup_for_withdraw(
2705+
token_program_id: Pubkey,
2706+
reserve_lamports: u64,
2707+
) -> (
2708+
ProgramTestContext,
2709+
StakePoolAccounts,
2710+
ValidatorStakeAccount,
2711+
DepositStakeAccount,
2712+
Keypair,
2713+
Keypair,
2714+
u64,
2715+
) {
2716+
let stake_pool_accounts = StakePoolAccounts::new_with_token_program(token_program_id);
2717+
let (
2718+
context,
2719+
validator_stake_account,
2720+
deposit_info,
2721+
user_transfer_authority,
2722+
user_stake_recipient,
2723+
tokens_to_withdraw,
2724+
) = setup_for_withdraw_with_accounts(&stake_pool_accounts, reserve_lamports).await;
2725+
26892726
(
26902727
context,
26912728
stake_pool_accounts,

program/tests/update_validator_list_balance.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,14 @@ async fn success_with_normal() {
201201

202202
// Simulate rewards
203203
for stake_account in &stake_accounts {
204-
context.increment_vote_account_credits(&stake_account.vote.pubkey(), 100);
204+
transfer(
205+
&mut context.banks_client,
206+
&context.payer,
207+
&context.last_blockhash,
208+
&stake_account.stake_account,
209+
1_000,
210+
)
211+
.await;
205212
}
206213

207214
// Warp one more epoch so the rewards are paid out

program/tests/withdraw_edge_cases.rs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,10 @@ async fn fail_withdraw_from_transient() {
916916
.await
917917
.unwrap();
918918

919+
let stake_minimum_delegation =
920+
stake_get_minimum_delegation(&mut context.banks_client, &context.payer, &last_blockhash)
921+
.await;
922+
919923
let rent = context.banks_client.get_rent().await.unwrap();
920924
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeStateV2>());
921925

@@ -927,7 +931,7 @@ async fn fail_withdraw_from_transient() {
927931
&last_blockhash,
928932
&validator_stake_account.stake_account,
929933
&validator_stake_account.transient_stake_account,
930-
deposit_info.stake_lamports + stake_rent - 2,
934+
deposit_info.stake_lamports + stake_rent - stake_minimum_delegation - 2,
931935
validator_stake_account.transient_stake_seed,
932936
DecreaseInstruction::Reserve,
933937
)
@@ -1531,3 +1535,95 @@ async fn success_remove_preferred_validator_resets_preference() {
15311535
"User should receive all lamports from removed validator"
15321536
);
15331537
}
1538+
1539+
#[tokio::test]
1540+
async fn fail_withdrawal_minimum_in_preferred() {
1541+
let stake_pool_accounts = StakePoolAccounts::new_without_fees();
1542+
1543+
let (
1544+
mut context,
1545+
validator_stake,
1546+
deposit_info,
1547+
user_transfer_authority,
1548+
user_stake_recipient,
1549+
tokens_to_burn,
1550+
) = setup_for_withdraw_with_accounts(&stake_pool_accounts, 0).await;
1551+
1552+
stake_pool_accounts
1553+
.set_preferred_validator(
1554+
&mut context.banks_client,
1555+
&context.payer,
1556+
&context.last_blockhash,
1557+
instruction::PreferredValidatorType::Withdraw,
1558+
Some(validator_stake.vote.pubkey()),
1559+
)
1560+
.await;
1561+
1562+
// Warp forward to activation
1563+
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
1564+
let slot = first_normal_slot + 1;
1565+
context.warp_to_slot(slot).unwrap();
1566+
let error = stake_pool_accounts
1567+
.update_all(
1568+
&mut context.banks_client,
1569+
&context.payer,
1570+
&context.last_blockhash,
1571+
false,
1572+
)
1573+
.await;
1574+
assert!(error.is_none());
1575+
1576+
// Withdraw some from preferred, get it down to 2x + 1
1577+
let stake_minimum_delegation = stake_get_minimum_delegation(
1578+
&mut context.banks_client,
1579+
&context.payer,
1580+
&context.last_blockhash,
1581+
)
1582+
.await;
1583+
1584+
let new_authority = Pubkey::new_unique();
1585+
let error = stake_pool_accounts
1586+
.withdraw_stake(
1587+
&mut context.banks_client,
1588+
&context.payer,
1589+
&context.last_blockhash,
1590+
&user_stake_recipient.pubkey(),
1591+
&user_transfer_authority,
1592+
&deposit_info.pool_account.pubkey(),
1593+
&validator_stake.stake_account,
1594+
&new_authority,
1595+
tokens_to_burn - stake_minimum_delegation,
1596+
)
1597+
.await;
1598+
assert!(error.is_none(), "{:?}", error);
1599+
1600+
let user_stake_recipient = Keypair::new();
1601+
create_blank_stake_account(
1602+
&mut context.banks_client,
1603+
&context.payer,
1604+
&context.last_blockhash,
1605+
&user_stake_recipient,
1606+
)
1607+
.await;
1608+
let error = stake_pool_accounts
1609+
.withdraw_stake(
1610+
&mut context.banks_client,
1611+
&context.payer,
1612+
&context.last_blockhash,
1613+
&user_stake_recipient.pubkey(),
1614+
&user_transfer_authority,
1615+
&deposit_info.pool_account.pubkey(),
1616+
&validator_stake.stake_account,
1617+
&new_authority,
1618+
stake_minimum_delegation,
1619+
)
1620+
.await;
1621+
let transaction_error = error.unwrap().unwrap();
1622+
assert_eq!(
1623+
transaction_error,
1624+
TransactionError::InstructionError(
1625+
0,
1626+
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
1627+
)
1628+
);
1629+
}

0 commit comments

Comments
 (0)