diff --git a/Cargo.lock b/Cargo.lock index 281735e5..b6308f74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -488,7 +488,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -499,7 +499,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2371,7 +2371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4969,9 +4969,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "fastbloom", @@ -5507,7 +5507,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5585,7 +5585,7 @@ dependencies = [ "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -11009,7 +11009,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -12010,7 +12010,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/clients/cli/src/quarantine.rs b/clients/cli/src/quarantine.rs index 5fa43427..878e42dc 100644 --- a/clients/cli/src/quarantine.rs +++ b/clients/cli/src/quarantine.rs @@ -63,6 +63,11 @@ pub async fn get_available_balances( stake_account_addresses: &[Pubkey], minimum_pool_balance: u64, ) -> Result, Error> { + let rent_exempt_reserve = config + .program_client + .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of()) + .await?; + let stake_accounts = config .rpc_client .get_multiple_accounts(stake_account_addresses) @@ -71,16 +76,25 @@ pub async fn get_available_balances( let mut delegations = vec![]; for stake_account in &stake_accounts { let delegation = if let Some(account) = stake_account { + // if this assert ever triggers, multistake or another account change has landed + // this function should be updated to use real stake account sizes + // we may be fetching hundreds of accounts here so memoize the rents + assert_eq!( + account.data.len(), + StakeStateV2::size_of(), + "StakeStateV2 is no longer canonical, or StakeStateV2::size_of() is no longer 200." + ); + match bincode::deserialize::(&account.data) { - Ok(StakeStateV2::Stake(meta, stake, _)) => ( + Ok(StakeStateV2::Stake(_, stake, _)) => ( stake.delegation.stake.saturating_sub(minimum_pool_balance), account .lamports .saturating_sub(stake.delegation.stake) - .saturating_sub(meta.rent_exempt_reserve), + .saturating_sub(rent_exempt_reserve), ), - Ok(StakeStateV2::Initialized(meta)) => { - (0, account.lamports.saturating_sub(meta.rent_exempt_reserve)) + Ok(StakeStateV2::Initialized(_)) => { + (0, account.lamports.saturating_sub(rent_exempt_reserve)) } _ => unreachable!(), } @@ -125,14 +139,14 @@ pub async fn create_uninitialized_stake_account_instruction( ) -> Result { let rent_amount = config .program_client - .get_minimum_balance_for_rent_exemption(std::mem::size_of::()) + .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of()) .await?; Ok(system_instruction::create_account( payer, stake_account, rent_amount, - std::mem::size_of::() as u64, + StakeStateV2::size_of() as u64, &stake::program::id(), )) } diff --git a/program/src/processor.rs b/program/src/processor.rs index 3fbb0215..dae12701 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -34,7 +34,7 @@ use { sysvar::stake_history::StakeHistorySysvar, }, solana_system_interface::{instruction as system_instruction, program as system_program}, - solana_sysvar::SysvarSerialize, + solana_sysvar::{Sysvar, SysvarSerialize}, solana_vote_interface::program as vote_program, spl_token_interface::{self as spl_token, state::Mint}, }; @@ -766,8 +766,13 @@ impl Processor { )?; check_stake_program(stake_program_info.key)?; + // we expect these numbers to be equal but get them separately in case of future changes + let rent = Rent::get()?; + let pool_rent_exempt_reserve = rent.minimum_balance(pool_stake_info.data_len()); + let onramp_rent_exempt_reserve = rent.minimum_balance(pool_onramp_info.data_len()); + // get main pool account, we require it to be fully active for most operations - let (pool_stake_meta, pool_stake_state) = get_stake_state(pool_stake_info)?; + let (_, pool_stake_state) = get_stake_state(pool_stake_info)?; let pool_stake_status = pool_stake_state .delegation .stake_activating_and_deactivating( @@ -779,17 +784,16 @@ impl Processor { // get on-ramp and its status. we have to match because unlike the main account it could be Initialized // if it doesnt exist, it must first be created with InitializePoolOnRamp - let (option_onramp_status, onramp_deactivation_epoch, onramp_rent_exempt_reserve) = + let (option_onramp_status, onramp_deactivation_epoch) = match try_from_slice_unchecked::(&pool_onramp_info.data.borrow()) { - Ok(StakeStateV2::Initialized(meta)) => (None, u64::MAX, meta.rent_exempt_reserve), - Ok(StakeStateV2::Stake(meta, stake, _)) => ( + Ok(StakeStateV2::Initialized(_)) => (None, u64::MAX), + Ok(StakeStateV2::Stake(_, stake, _)) => ( Some(stake.delegation.stake_activating_and_deactivating( clock.epoch, stake_history, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, )), stake.delegation.deactivation_epoch, - meta.rent_exempt_reserve, ), _ => return Err(SinglePoolError::OnRampDoesntExist.into()), }; @@ -831,7 +835,7 @@ impl Processor { let pool_excess_lamports = pool_stake_info .lamports() .saturating_sub(pool_stake_state.delegation.stake) - .saturating_sub(pool_stake_meta.rent_exempt_reserve); + .saturating_sub(pool_rent_exempt_reserve); // if the on-ramp is fully active, move its stake to the main pool account if let Some(ref onramp_status) = option_onramp_status { @@ -975,9 +979,12 @@ impl Processor { return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into()); } + let rent = Rent::get()?; + let pool_rent_exempt_reserve = rent.minimum_balance(pool_stake_info.data_len()); + let minimum_pool_balance = minimum_pool_balance()?; - let (pool_stake_meta, pool_stake_state) = get_stake_state(pool_stake_info)?; + let (_, pool_stake_state) = get_stake_state(pool_stake_info)?; let pre_pool_stake = pool_stake_state .delegation .stake @@ -985,7 +992,7 @@ impl Processor { let pre_pool_excess_lamports = pool_stake_info .lamports() .checked_sub(pool_stake_state.delegation.stake) - .and_then(|amount| amount.checked_sub(pool_stake_meta.rent_exempt_reserve)) + .and_then(|amount| amount.checked_sub(pool_rent_exempt_reserve)) .ok_or(SinglePoolError::ArithmeticOverflow)?; msg!("Available stake pre merge {}", pre_pool_stake); @@ -1013,7 +1020,7 @@ impl Processor { stake_history_info.clone(), )?; - let (pool_stake_meta, pool_stake_state) = get_stake_state(pool_stake_info)?; + let (_, pool_stake_state) = get_stake_state(pool_stake_info)?; let post_pool_stake = pool_stake_state .delegation .stake @@ -1030,7 +1037,7 @@ impl Processor { // this includes their rent-exempt reserve if the pool is fully active let user_excess_lamports = post_pool_lamports .checked_sub(pool_stake_state.delegation.stake) - .and_then(|amount| amount.checked_sub(pool_stake_meta.rent_exempt_reserve)) + .and_then(|amount| amount.checked_sub(pool_rent_exempt_reserve)) .and_then(|amount| amount.checked_sub(pre_pool_excess_lamports)) .ok_or(SinglePoolError::ArithmeticOverflow)?; diff --git a/program/tests/deposit.rs b/program/tests/deposit.rs index dd830133..9252041f 100644 --- a/program/tests/deposit.rs +++ b/program/tests/deposit.rs @@ -6,7 +6,7 @@ use { helpers::*, solana_program_test::*, solana_sdk::{signature::Signer, signer::keypair::Keypair, transaction::Transaction}, - solana_stake_interface::state::{Authorized, Lockup}, + solana_stake_interface::state::{Authorized, Lockup, StakeStateV2}, solana_system_interface::instruction as system_instruction, spl_associated_token_account_interface::address::get_associated_token_address, spl_single_pool::{error::SinglePoolError, id, instruction}, @@ -38,6 +38,9 @@ async fn success( }; let mut context = program_test.start_with_context().await; + let rent = context.banks_client.get_rent().await.unwrap(); + let rent_exempt_reserve = rent.minimum_balance(StakeStateV2::size_of()); + let accounts = SinglePoolAccounts::default(); accounts .initialize_for_deposit( @@ -107,7 +110,7 @@ async fn success( .unwrap(); } - let (alice_meta_before_deposit, alice_stake_before_deposit, _) = + let (_, alice_stake_before_deposit, _) = get_stake_account(&mut context.banks_client, &accounts.alice_stake.pubkey()).await; let alice_stake_before_deposit = alice_stake_before_deposit.unwrap().delegation.stake; @@ -141,7 +144,7 @@ async fn success( .await .lamports; - let (pool_meta_after, pool_stake_after, pool_lamports_after) = + let (_, pool_stake_after, pool_lamports_after) = get_stake_account(&mut context.banks_client, &accounts.stake_account).await; let pool_stake_after = pool_stake_after.unwrap().delegation.stake; @@ -150,7 +153,7 @@ async fn success( let expected_deposit = if activate { alice_stake_before_deposit } else { - alice_stake_before_deposit + alice_meta_before_deposit.rent_exempt_reserve + alice_stake_before_deposit + rent_exempt_reserve }; // deposit stake account is closed @@ -168,10 +171,7 @@ async fn success( assert_eq!(pool_lamports_after, pool_lamports_before + expected_deposit); assert_eq!( pool_lamports_after, - pool_stake_before - + expected_deposit - + pool_meta_after.rent_exempt_reserve - + pool_extra_lamports, + pool_stake_before + expected_deposit + rent_exempt_reserve + pool_extra_lamports, ); // alice got her rent and extra back if active, or just extra back otherwise diff --git a/program/tests/replenish.rs b/program/tests/replenish.rs index 062340d2..9bdbc040 100644 --- a/program/tests/replenish.rs +++ b/program/tests/replenish.rs @@ -218,6 +218,10 @@ async fn move_value_success( }; let mut context = program_test.start_with_context().await; + let rent = context.banks_client.get_rent().await.unwrap(); + let pool_rent = rent.minimum_balance(StakeStateV2::size_of()); + let onramp_rent = pool_rent; + let accounts = SinglePoolAccounts::default(); accounts .initialize_for_deposit(&mut context, TEST_STAKE_AMOUNT, None) @@ -297,15 +301,14 @@ async fn move_value_success( .await .unwrap(); - let (pool_meta, pool_stake, pool_lamports) = + let (_, pool_stake, pool_lamports) = get_stake_account(&mut context.banks_client, &accounts.stake_account).await; let pool_status = pool_stake .unwrap() .delegation .stake_activating_and_deactivating(clock.epoch, &stake_history, Some(0)); - let pool_rent = pool_meta.rent_exempt_reserve; - let (onramp_meta, onramp_stake, onramp_lamports) = + let (_, onramp_stake, onramp_lamports) = get_stake_account(&mut context.banks_client, &accounts.onramp_account).await; let onramp_status = onramp_stake .map(|stake| { @@ -314,7 +317,6 @@ async fn move_value_success( .stake_activating_and_deactivating(clock.epoch, &stake_history, Some(0)) }) .unwrap_or_default(); - let onramp_rent = onramp_meta.rent_exempt_reserve; match (onramp_state, move_lamports_to_onramp) { // stake moved already before test or because of test, new lamports were added to onramp