Quellcode durchsuchen

remove stake program builtin (#7203)

* remove stake program from `BUILTINS` and `MIGRATING_BUILTINS_COSTS`
* add bpf state program to `CORE_BPF_PROGRAMS`
* use bpf program in solana-test-validator, `ProgramTest`, and `LocalCluster`
* add stake program repo to core bpf genesis script
* break core bpf and spl binaries out into new solana-program-binaries crate
* fix bank-based tests that no longer have a stake program
hana vor 3 Monaten
Ursprung
Commit
eec3878cef
46 geänderte Dateien mit 397 neuen und 2292 gelöschten Zeilen
  1. 24 23
      Cargo.lock
  2. 2 1
      Cargo.toml
  3. 2 1
      builtins-default-costs/Cargo.toml
  4. 28 24
      builtins-default-costs/src/lib.rs
  5. 7 26
      builtins/src/lib.rs
  6. 27 11
      compute-budget-instruction/src/builtin_programs_filter.rs
  7. 61 54
      compute-budget-instruction/src/compute_budget_instruction_details.rs
  8. 1 0
      core/Cargo.toml
  9. 20 18
      core/tests/scheduler_cost_adjustment.rs
  10. 1 0
      fetch-core-bpf.sh
  11. 28 14
      ledger/src/staking_utils.rs
  12. 2 0
      local-cluster/Cargo.toml
  13. 8 0
      local-cluster/src/local_cluster.rs
  14. 19 0
      program-binaries/Cargo.toml
  15. 36 5
      program-binaries/src/lib.rs
  16. 0 0
      program-binaries/src/programs/core_bpf_address_lookup_table-3.0.0.so
  17. 0 0
      program-binaries/src/programs/core_bpf_config-3.0.0.so
  18. 0 0
      program-binaries/src/programs/core_bpf_feature_gate-0.0.1.so
  19. BIN
      program-binaries/src/programs/core_bpf_stake-1.0.0.so
  20. 0 0
      program-binaries/src/programs/spl_associated_token_account-1.1.1.so
  21. 0 0
      program-binaries/src/programs/spl_memo-1.0.0.so
  22. 0 0
      program-binaries/src/programs/spl_memo-3.0.0.so
  23. 0 0
      program-binaries/src/programs/spl_token-3.5.0.so
  24. 0 0
      program-binaries/src/programs/spl_token_2022-8.0.0.so
  25. 1 0
      program-test/Cargo.toml
  26. 1 2
      program-test/src/lib.rs
  27. 3 2
      program-test/tests/core_bpf.rs
  28. 2 1
      program-test/tests/spl.rs
  29. 16 0
      programs/sbf/Cargo.lock
  30. 0 29
      programs/sbf/tests/programs.rs
  31. 0 34
      programs/stake-tests/Cargo.toml
  32. 0 1222
      programs/stake-tests/tests/test_move_stake_and_lamports.rs
  33. 40 53
      rpc/src/rpc_pubsub.rs
  34. 1 0
      runtime/Cargo.toml
  35. 2 3
      runtime/src/bank/builtin_programs.rs
  36. 0 1
      runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs
  37. 3 38
      runtime/src/bank/partitioned_epoch_rewards/mod.rs
  38. 27 35
      runtime/src/bank/tests.rs
  39. 1 1
      runtime/src/genesis_utils.rs
  40. 0 670
      runtime/tests/stake.rs
  41. 2 0
      stake-accounts/Cargo.toml
  42. 21 1
      stake-accounts/src/stake_accounts.rs
  43. 1 0
      svm/Cargo.toml
  44. 4 12
      svm/tests/integration_test.rs
  45. 1 0
      test-validator/Cargo.toml
  46. 5 11
      test-validator/src/lib.rs

+ 24 - 23
Cargo.lock

@@ -7526,6 +7526,7 @@ dependencies = [
  "agave-feature-set",
  "ahash 0.8.11",
  "log",
+ "qualifier_attr",
  "rand 0.8.5",
  "solana-bpf-loader-program",
  "solana-compute-budget-program",
@@ -8134,6 +8135,7 @@ dependencies = [
  "solana-perf",
  "solana-poh",
  "solana-poh-config",
+ "solana-program-binaries",
  "solana-program-runtime",
  "solana-pubkey",
  "solana-quic-client",
@@ -9210,9 +9212,11 @@ dependencies = [
  "solana-native-token",
  "solana-net-utils",
  "solana-poh-config",
+ "solana-program-binaries",
  "solana-pubkey",
  "solana-pubsub-client",
  "solana-quic-client",
+ "solana-rent",
  "solana-rpc-client",
  "solana-rpc-client-api",
  "solana-runtime",
@@ -9616,6 +9620,20 @@ dependencies = [
  "solana-sysvar-id",
 ]
 
+[[package]]
+name = "solana-program-binaries"
+version = "3.0.0"
+dependencies = [
+ "bincode",
+ "serde",
+ "solana-account",
+ "solana-loader-v3-interface",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+ "spl-generic-token",
+]
+
 [[package]]
 name = "solana-program-entrypoint"
 version = "3.1.0"
@@ -9752,6 +9770,7 @@ dependencies = [
  "solana-native-token",
  "solana-poh-config",
  "solana-program",
+ "solana-program-binaries",
  "solana-program-entrypoint",
  "solana-program-error",
  "solana-program-runtime",
@@ -10279,6 +10298,7 @@ dependencies = [
  "solana-perf",
  "solana-poh-config",
  "solana-precompile-error",
+ "solana-program-binaries",
  "solana-program-runtime",
  "solana-pubkey",
  "solana-rayon-threadlimit",
@@ -10644,6 +10664,7 @@ dependencies = [
  "solana-keypair",
  "solana-message",
  "solana-native-token",
+ "solana-program-binaries",
  "solana-pubkey",
  "solana-remote-wallet",
  "solana-rpc-client",
@@ -10653,6 +10674,7 @@ dependencies = [
  "solana-signer",
  "solana-stake-interface",
  "solana-stake-program",
+ "solana-sysvar",
  "solana-transaction",
  "solana-version",
 ]
@@ -10717,29 +10739,6 @@ dependencies = [
  "test-case",
 ]
 
-[[package]]
-name = "solana-stake-program-tests"
-version = "3.0.0"
-dependencies = [
- "agave-feature-set",
- "assert_matches",
- "bincode",
- "solana-account",
- "solana-instruction",
- "solana-keypair",
- "solana-program-error",
- "solana-program-test",
- "solana-pubkey",
- "solana-signer",
- "solana-stake-interface",
- "solana-system-interface",
- "solana-sysvar",
- "solana-transaction",
- "solana-transaction-error",
- "solana-vote-program",
- "test-case",
-]
-
 [[package]]
 name = "solana-storage-bigtable"
 version = "3.0.0"
@@ -10902,6 +10901,7 @@ dependencies = [
  "solana-nonce",
  "solana-nonce-account",
  "solana-precompile-error",
+ "solana-program-binaries",
  "solana-program-entrypoint",
  "solana-program-pack",
  "solana-program-runtime",
@@ -11155,6 +11155,7 @@ dependencies = [
  "solana-message",
  "solana-native-token",
  "solana-net-utils",
+ "solana-program-binaries",
  "solana-program-test",
  "solana-pubkey",
  "solana-rent",

+ 2 - 1
Cargo.toml

@@ -66,6 +66,7 @@ members = [
     "poh-bench",
     "poseidon",
     "precompiles",
+    "program-binaries",
     "program-runtime",
     "program-test",
     "programs/bpf-loader-tests",
@@ -75,7 +76,6 @@ members = [
     "programs/ed25519-tests",
     "programs/loader-v4",
     "programs/stake",
-    "programs/stake-tests",
     "programs/system",
     "programs/vote",
     "programs/zk-elgamal-proof",
@@ -473,6 +473,7 @@ solana-poseidon = { path = "poseidon", version = "=3.0.0" }
 solana-precompile-error = "3.0.0"
 solana-presigner = "3.0.0"
 solana-program = { version = "3.0.0", default-features = false }
+solana-program-binaries = { path = "program-binaries", version = "=3.0.0" }
 solana-program-entrypoint = "3.1.0"
 solana-program-error = "3.0.0"
 solana-program-memory = "3.0.0"

+ 2 - 1
builtins-default-costs/Cargo.toml

@@ -19,12 +19,13 @@ name = "solana_builtins_default_costs"
 
 [features]
 frozen-abi = ["dep:solana-frozen-abi", "solana-vote-program/frozen-abi"]
-dev-context-only-utils = []
+dev-context-only-utils = ["dep:qualifier_attr"]
 
 [dependencies]
 agave-feature-set = { workspace = true }
 ahash = { workspace = true }
 log = { workspace = true }
+qualifier_attr = { workspace = true, optional = true }
 solana-bpf-loader-program = { workspace = true }
 solana-compute-budget-program = { workspace = true }
 solana-frozen-abi = { workspace = true, optional = true, features = [

+ 28 - 24
builtins-default-costs/src/lib.rs

@@ -1,16 +1,22 @@
 #![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
 #![allow(clippy::arithmetic_side_effects)]
+
+#[cfg(feature = "dev-context-only-utils")]
+use qualifier_attr::field_qualifiers;
 use {
-    agave_feature_set::{self as feature_set},
     ahash::AHashMap,
     solana_pubkey::Pubkey,
     solana_sdk_ids::{
         bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, compute_budget, ed25519_program,
-        loader_v4, secp256k1_program, stake, system_program, vote,
+        loader_v4, secp256k1_program, system_program, vote,
     },
 };
 
 #[derive(Clone)]
+#[cfg_attr(
+    feature = "dev-context-only-utils",
+    field_qualifiers(core_bpf_migration_feature(pub), position(pub))
+)]
 pub struct MigratingBuiltinCost {
     core_bpf_migration_feature: Pubkey,
     // encoding positional information explicitly for migration feature item,
@@ -77,20 +83,16 @@ static BUILTIN_INSTRUCTION_COSTS: std::sync::LazyLock<AHashMap<Pubkey, BuiltinCo
 /// correctly furnishing `core_bpf_migration_feature`.
 ///
 #[allow(dead_code)]
-const TOTAL_COUNT_BUILTINS: usize = 10;
+const TOTAL_COUNT_BUILTINS: usize = 9;
 #[cfg(test)]
 static_assertions::const_assert_eq!(
     MIGRATING_BUILTINS_COSTS.len() + NON_MIGRATING_BUILTINS_COSTS.len(),
     TOTAL_COUNT_BUILTINS
 );
 
-pub const MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[(
-    stake::id(),
-    BuiltinCost::Migrating(MigratingBuiltinCost {
-        core_bpf_migration_feature: feature_set::migrate_stake_program_to_core_bpf::id(),
-        position: 0,
-    }),
-)];
+/// MIGRATING_BUILTINS_COSTS is empty as no builtins are presently being migrated.
+/// We leave it and the related scaffolding in place for future planned migrations.
+pub const MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[];
 
 const NON_MIGRATING_BUILTINS_COSTS: &[(Pubkey, BuiltinCost)] = &[
     (vote::id(), BuiltinCost::NotMigrating),
@@ -200,20 +202,22 @@ mod test {
             get_builtin_migration_feature_index(&compute_budget::id()),
             BuiltinMigrationFeatureIndex::BuiltinNoMigrationFeature,
         ));
-        let feature_index = get_builtin_migration_feature_index(&stake::id());
-        assert!(matches!(
-            feature_index,
-            BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
-        ));
-        let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
-            feature_index
-        else {
-            panic!("expect migrating builtin")
-        };
-        assert_eq!(
-            get_migration_feature_id(feature_index),
-            &feature_set::migrate_stake_program_to_core_bpf::id()
-        );
+        for (program_id, migrating_builtin) in MIGRATING_BUILTINS_COSTS {
+            let feature_index = get_builtin_migration_feature_index(program_id);
+            assert!(matches!(
+                feature_index,
+                BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(_)
+            ));
+            let BuiltinMigrationFeatureIndex::BuiltinWithMigrationFeature(feature_index) =
+                feature_index
+            else {
+                panic!("expect migrating builtin")
+            };
+            assert_eq!(
+                get_migration_feature_id(feature_index),
+                migrating_builtin.core_bpf_migration_feature().unwrap(),
+            );
+        }
     }
 
     #[test]

+ 7 - 26
builtins/src/lib.rs

@@ -64,20 +64,6 @@ pub static BUILTINS: &[BuiltinPrototype] = &[
         program_id: solana_vote_program::id(),
         entrypoint: solana_vote_program::vote_processor::Entrypoint::vm,
     }),
-    BuiltinPrototype {
-        core_bpf_migration_config: Some(CoreBpfMigrationConfig {
-            source_buffer_address: buffer_accounts::stake_program::id(),
-            upgrade_authority_address: None,
-            feature_id: agave_feature_set::migrate_stake_program_to_core_bpf::id(),
-            verified_build_hash: None,
-            migration_target: CoreBpfMigrationTargetType::Builtin,
-            datapoint_name: "migrate_builtin_to_core_bpf_stake_program",
-        }),
-        name: "stake_program",
-        enable_feature_id: None,
-        program_id: solana_stake_program::id(),
-        entrypoint: solana_stake_program::stake_instruction::Entrypoint::vm,
-    },
     testable_prototype!(BuiltinPrototype {
         core_bpf_migration_config: None,
         name: solana_bpf_loader_deprecated_program,
@@ -144,9 +130,6 @@ pub static STATELESS_BUILTINS: &[StatelessBuiltinPrototype] = &[StatelessBuiltin
 
 /// Live source buffer accounts for builtin migrations.
 mod buffer_accounts {
-    pub mod stake_program {
-        solana_pubkey::declare_id!("8t3vv6v99tQA6Gp7fVdsBH66hQMaswH5qsJVqJqo8xvG");
-    }
     pub mod slashing_program {
         use {solana_hash::Hash, solana_pubkey::Pubkey};
 
@@ -370,34 +353,32 @@ mod tests {
             &super::BUILTINS[1].core_bpf_migration_config,
             &Some(super::test_only::vote_program::CONFIG)
         );
-        // Stake has a live migration config, so it has no test-only configs
-        // to test here.
         assert_eq!(
-            &super::BUILTINS[3].core_bpf_migration_config,
+            &super::BUILTINS[2].core_bpf_migration_config,
             &Some(super::test_only::solana_bpf_loader_deprecated_program::CONFIG)
         );
         assert_eq!(
-            &super::BUILTINS[4].core_bpf_migration_config,
+            &super::BUILTINS[3].core_bpf_migration_config,
             &Some(super::test_only::solana_bpf_loader_program::CONFIG)
         );
         assert_eq!(
-            &super::BUILTINS[5].core_bpf_migration_config,
+            &super::BUILTINS[4].core_bpf_migration_config,
             &Some(super::test_only::solana_bpf_loader_upgradeable_program::CONFIG)
         );
         assert_eq!(
-            &super::BUILTINS[6].core_bpf_migration_config,
+            &super::BUILTINS[5].core_bpf_migration_config,
             &Some(super::test_only::compute_budget_program::CONFIG)
         );
         assert_eq!(
-            &super::BUILTINS[7].core_bpf_migration_config,
+            &super::BUILTINS[6].core_bpf_migration_config,
             &Some(super::test_only::zk_token_proof_program::CONFIG)
         );
         assert_eq!(
-            &super::BUILTINS[8].core_bpf_migration_config,
+            &super::BUILTINS[7].core_bpf_migration_config,
             &Some(super::test_only::loader_v4::CONFIG)
         );
         assert_eq!(
-            &super::BUILTINS[9].core_bpf_migration_config,
+            &super::BUILTINS[8].core_bpf_migration_config,
             &Some(super::test_only::zk_elgamal_proof_program::CONFIG)
         );
     }

+ 27 - 11
compute-budget-instruction/src/builtin_programs_filter.rs

@@ -64,8 +64,10 @@ impl BuiltinProgramsFilter {
 #[cfg(test)]
 mod test {
     use {
-        super::*, agave_feature_set as feature_set,
-        solana_builtins_default_costs::get_migration_feature_position,
+        super::*,
+        solana_builtins_default_costs::{
+            get_migration_feature_id, BuiltinCost, MigratingBuiltinCost, MIGRATING_BUILTINS_COSTS,
+        },
     };
 
     const DUMMY_PROGRAM_ID: &str = "dummmy1111111111111111111111111111111111111";
@@ -110,15 +112,29 @@ mod test {
         );
 
         // migrating builtins
-        index += 1;
-        assert_eq!(
-            test_store.get_program_kind(index, &solana_sdk_ids::stake::id()),
-            ProgramKind::MigratingBuiltin {
-                core_bpf_migration_feature_index: get_migration_feature_position(
-                    &feature_set::migrate_stake_program_to_core_bpf::id()
-                ),
-            }
-        );
+        for (program_id, migrating_builtin) in MIGRATING_BUILTINS_COSTS {
+            index += 1;
+
+            let BuiltinCost::Migrating(MigratingBuiltinCost {
+                core_bpf_migration_feature,
+                position,
+            }) = migrating_builtin
+            else {
+                panic!("MIGRATING_BUILTINS_COSTS must only contain BuiltinCost::Migrating");
+            };
+
+            assert_eq!(
+                get_migration_feature_id(*position),
+                core_bpf_migration_feature
+            );
+
+            assert_eq!(
+                test_store.get_program_kind(index, program_id),
+                ProgramKind::MigratingBuiltin {
+                    core_bpf_migration_feature_index: *position,
+                }
+            );
+        }
     }
 
     #[test]

+ 61 - 54
compute-budget-instruction/src/compute_budget_instruction_details.rs

@@ -223,7 +223,9 @@ impl ComputeBudgetInstructionDetails {
 mod test {
     use {
         super::*,
-        solana_builtins_default_costs::get_migration_feature_position,
+        solana_builtins_default_costs::{
+            get_migration_feature_position, BuiltinCost, MigratingBuiltinCost,
+        },
         solana_instruction::Instruction,
         solana_keypair::Keypair,
         solana_message::Message,
@@ -514,58 +516,63 @@ mod test {
 
     #[test]
     fn test_builtin_program_migration() {
-        let tx = build_sanitized_transaction(&[
-            Instruction::new_with_bincode(Pubkey::new_unique(), &(), vec![]),
-            solana_stake_interface::instruction::delegate_stake(
-                &Pubkey::new_unique(),
-                &Pubkey::new_unique(),
-                &Pubkey::new_unique(),
-            ),
-        ]);
-        let feature_id_index = get_migration_feature_position(
-            &agave_feature_set::migrate_stake_program_to_core_bpf::id(),
-        );
-        let mut expected_details = ComputeBudgetInstructionDetails {
-            num_non_compute_budget_instructions: Saturating(2),
-            num_non_builtin_instructions: Saturating(1),
-            ..ComputeBudgetInstructionDetails::default()
-        };
-        expected_details
-            .migrating_builtin_feature_counters
-            .migrating_builtin[feature_id_index] = Saturating(1);
-        let expected_details = Ok(expected_details);
-        let details =
-            ComputeBudgetInstructionDetails::try_from(SVMMessage::program_instructions_iter(&tx));
-        assert_eq!(details, expected_details);
-        let details = details.unwrap();
-
-        let mut feature_set = FeatureSet::default();
-
-        // migrate_stake_program_to_core_bpf: false;
-        // expect: 1 bpf ix, 1 non-compute-budget builtin, cu-limit = 200K + 3K
-        let cu_limits = details.sanitize_and_convert_to_compute_budget_limits(&feature_set);
-        assert_eq!(
-            cu_limits,
-            Ok(ComputeBudgetLimits {
-                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
-                    + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
-                ..ComputeBudgetLimits::default()
-            })
-        );
-
-        // migrate_stake_program_to_core_bpf: true;
-        // expect: 2 bpf ix, cu-limit = 2 * 200K
-        feature_set.activate(
-            &agave_feature_set::migrate_stake_program_to_core_bpf::id(),
-            0,
-        );
-        let cu_limits = details.sanitize_and_convert_to_compute_budget_limits(&feature_set);
-        assert_eq!(
-            cu_limits,
-            Ok(ComputeBudgetLimits {
-                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 2,
-                ..ComputeBudgetLimits::default()
-            })
-        );
+        for (program_id, builtin_cost) in MIGRATING_BUILTINS_COSTS {
+            let BuiltinCost::Migrating(MigratingBuiltinCost {
+                core_bpf_migration_feature: feature_id,
+                position,
+            }) = builtin_cost
+            else {
+                panic!("MIGRATING_BUILTINS_COSTS must only contain BuiltinCost::Migrating");
+            };
+
+            assert_eq!(get_migration_feature_id(*position), feature_id);
+            assert_eq!(get_migration_feature_position(feature_id), *position);
+
+            let tx = build_sanitized_transaction(&[
+                Instruction::new_with_bincode(Pubkey::new_unique(), &(), vec![]),
+                Instruction::new_with_bincode(*program_id, &(), vec![]),
+            ]);
+
+            let mut expected_details = ComputeBudgetInstructionDetails {
+                num_non_compute_budget_instructions: Saturating(2),
+                num_non_builtin_instructions: Saturating(1),
+                ..ComputeBudgetInstructionDetails::default()
+            };
+            expected_details
+                .migrating_builtin_feature_counters
+                .migrating_builtin[*position] = Saturating(1);
+            let expected_details = Ok(expected_details);
+            let details = ComputeBudgetInstructionDetails::try_from(
+                SVMMessage::program_instructions_iter(&tx),
+            );
+            assert_eq!(details, expected_details);
+            let details = details.unwrap();
+
+            let mut feature_set = FeatureSet::default();
+
+            // migrate bpf program: false;
+            // expect: 1 bpf ix, 1 non-compute-budget builtin, cu-limit = 200K + 3K
+            let cu_limits = details.sanitize_and_convert_to_compute_budget_limits(&feature_set);
+            assert_eq!(
+                cu_limits,
+                Ok(ComputeBudgetLimits {
+                    compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
+                        + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
+                    ..ComputeBudgetLimits::default()
+                })
+            );
+
+            // migrate bpf program: true;
+            // expect: 2 bpf ix, cu-limit = 2 * 200K
+            feature_set.activate(feature_id, 0);
+            let cu_limits = details.sanitize_and_convert_to_compute_budget_limits(&feature_set);
+            assert_eq!(
+                cu_limits,
+                Ok(ComputeBudgetLimits {
+                    compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 2,
+                    ..ComputeBudgetLimits::default()
+                })
+            );
+        }
     }
 }

+ 1 - 0
core/Cargo.toml

@@ -201,6 +201,7 @@ solana-ledger = { workspace = true, features = ["dev-context-only-utils"] }
 solana-logger = { workspace = true }
 solana-net-utils = { workspace = true, features = ["dev-context-only-utils"] }
 solana-poh = { workspace = true, features = ["dev-context-only-utils"] }
+solana-program-binaries = { workspace = true }
 solana-program-runtime = { workspace = true, features = ["metrics"] }
 solana-rpc = { workspace = true, features = ["dev-context-only-utils"] }
 solana-stake-program = { workspace = true }

+ 20 - 18
core/tests/scheduler_cost_adjustment.rs

@@ -1,6 +1,6 @@
 #![cfg(test)]
 use {
-    solana_account::Account,
+    solana_account::{Account, ReadableAccount},
     solana_clock::{Slot, MAX_PROCESSING_AGE},
     solana_compute_budget::compute_budget_limits::MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
     solana_compute_budget_interface::ComputeBudgetInstruction,
@@ -15,7 +15,7 @@ use {
     solana_rent::Rent,
     solana_runtime::{bank::Bank, bank_forks::BankForks},
     solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
-    solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable, secp256k1_program},
+    solana_sdk_ids::{bpf_loader_upgradeable, secp256k1_program},
     solana_signer::Signer,
     solana_svm::transaction_processor::ExecutionRecordingConfig,
     solana_svm_timings::ExecuteTimings,
@@ -25,8 +25,6 @@ use {
     std::sync::{Arc, RwLock},
 };
 
-const MEMO_PROGRAM_ELF: &[u8] = include_bytes!("../../program-test/src/programs/spl_memo-3.0.0.so");
-
 fn new_bank_from_parent_with_bank_forks(
     bank_forks: &RwLock<BankForks>,
     parent: Arc<Bank>,
@@ -67,17 +65,14 @@ impl TestSetup {
     }
 
     fn install_memo_program_account(&mut self) {
-        self.genesis_config.accounts.insert(
-            spl_memo_interface::v3::id(),
-            Account {
-                lamports: u64::MAX,
-                // borrows memo elf for executing memo ix in order to set up test condition
-                data: MEMO_PROGRAM_ELF.to_vec(),
-                owner: bpf_loader::id(),
-                executable: true,
-                rent_epoch: 0,
-            },
-        );
+        let (pubkey, account) = solana_program_binaries::by_id(
+            &spl_memo_interface::v3::id(),
+            &self.genesis_config.rent,
+        )
+        .unwrap()
+        .swap_remove(0);
+
+        self.genesis_config.add_account(pubkey, account);
     }
 
     fn execute_test_transaction(&mut self, ixs: &[Instruction]) -> TestResult {
@@ -167,10 +162,17 @@ impl TestSetup {
         let payer_address = self.mint_keypair.pubkey();
         let upgrade_authority_address = payer_address;
 
+        let (_, memo) = solana_program_binaries::by_id(
+            &spl_memo_interface::v3::id(),
+            &self.genesis_config.rent,
+        )
+        .unwrap()
+        .swap_remove(0);
+
         // Stash a valid buffer account before attempting a deployment.
         {
             let metadata_offset = UpgradeableLoaderState::size_of_buffer_metadata();
-            let space = UpgradeableLoaderState::size_of_buffer(MEMO_PROGRAM_ELF.len());
+            let space = UpgradeableLoaderState::size_of_buffer(memo.data().len());
             let lamports = self.genesis_config.rent.minimum_balance(space);
 
             let mut data = vec![0; space];
@@ -181,7 +183,7 @@ impl TestSetup {
                 },
             )
             .unwrap();
-            data[metadata_offset..].copy_from_slice(MEMO_PROGRAM_ELF);
+            data[metadata_offset..].copy_from_slice(memo.data());
 
             self.genesis_config.accounts.insert(
                 buffer_address,
@@ -217,7 +219,7 @@ impl TestSetup {
             &buffer_address,
             &upgrade_authority_address,
             /* program_lamports */ 0, // Doesn't matter here.
-            /* max_data_len */ MEMO_PROGRAM_ELF.len().saturating_mul(2),
+            /* max_data_len */ memo.data().len().saturating_mul(2),
         )
         .unwrap()
         .pop()

+ 1 - 0
fetch-core-bpf.sh

@@ -29,5 +29,6 @@ add_core_bpf_program_to_fetch() {
 add_core_bpf_program_to_fetch address-lookup-table 3.0.0 AddressLookupTab1e1111111111111111111111111 BPFLoaderUpgradeab1e11111111111111111111111
 add_core_bpf_program_to_fetch config 3.0.0 Config1111111111111111111111111111111111111 BPFLoaderUpgradeab1e11111111111111111111111
 add_core_bpf_program_to_fetch feature-gate 0.0.1 Feature111111111111111111111111111111111111 BPFLoaderUpgradeab1e11111111111111111111111
+add_core_bpf_program_to_fetch stake 1.0.0 Stake11111111111111111111111111111111111111 BPFLoaderUpgradeab1e11111111111111111111111
 
 fetch_programs "$PREFIX" "${programs[@]}"

+ 28 - 14
ledger/src/staking_utils.rs

@@ -2,7 +2,7 @@
 pub(crate) mod tests {
     use {
         rand::Rng,
-        solana_account::AccountSharedData,
+        solana_account::{AccountSharedData, WritableAccount},
         solana_clock::Clock,
         solana_instruction::Instruction,
         solana_keypair::Keypair,
@@ -10,8 +10,9 @@ pub(crate) mod tests {
         solana_runtime::bank::Bank,
         solana_signer::{signers::Signers, Signer},
         solana_stake_interface::{
-            instruction as stake_instruction,
-            state::{Authorized, Lockup},
+            program as stake_program,
+            stake_flags::StakeFlags,
+            state::{Authorized, Delegation, Meta, Stake, StakeStateV2},
         },
         solana_transaction::Transaction,
         solana_vote::vote_account::{VoteAccount, VoteAccounts},
@@ -62,18 +63,31 @@ pub(crate) mod tests {
         let stake_account_keypair = Keypair::new();
         let stake_account_pubkey = stake_account_keypair.pubkey();
 
-        process_instructions(
-            bank,
-            &[from_account, &stake_account_keypair],
-            &stake_instruction::create_account_and_delegate_stake(
-                &from_account.pubkey(),
-                &stake_account_pubkey,
-                &vote_pubkey,
-                &Authorized::auto(&stake_account_pubkey),
-                &Lockup::default(),
-                amount,
-            ),
+        let stake_account = StakeStateV2::Stake(
+            Meta {
+                authorized: Authorized::auto(&stake_account_pubkey),
+                ..Meta::default()
+            },
+            Stake {
+                delegation: Delegation {
+                    voter_pubkey: vote_pubkey,
+                    stake: amount,
+                    ..Delegation::default()
+                },
+                ..Stake::default()
+            },
+            StakeFlags::default(),
         );
+
+        let account = AccountSharedData::create(
+            1,
+            bincode::serialize(&stake_account).unwrap(),
+            stake_program::id(),
+            false,
+            u64::MAX,
+        );
+
+        bank.store_account(&stake_account_pubkey, &account);
     }
 
     #[test]

+ 2 - 0
local-cluster/Cargo.toml

@@ -42,9 +42,11 @@ solana-message = { workspace = true }
 solana-native-token = { workspace = true }
 solana-net-utils = { workspace = true }
 solana-poh-config = { workspace = true }
+solana-program-binaries = { workspace = true }
 solana-pubkey = { workspace = true }
 solana-pubsub-client = { workspace = true }
 solana-quic-client = { workspace = true }
+solana-rent = { workspace = true }
 solana-rpc-client = { workspace = true }
 solana-rpc-client-api = { workspace = true }
 solana-runtime = { workspace = true }

+ 8 - 0
local-cluster/src/local_cluster.rs

@@ -30,7 +30,9 @@ use {
     solana_native_token::LAMPORTS_PER_SOL,
     solana_net_utils::sockets::bind_to_localhost_unique,
     solana_poh_config::PohConfig,
+    solana_program_binaries::core_bpf_programs,
     solana_pubkey::Pubkey,
+    solana_rent::Rent,
     solana_rpc_client::rpc_client::RpcClient,
     solana_runtime::{
         genesis_utils::{
@@ -252,6 +254,12 @@ impl LocalCluster {
             }
         };
 
+        for core_program_account in &core_bpf_programs(&Rent::default(), |_| true) {
+            config
+                .additional_accounts
+                .push(core_program_account.clone());
+        }
+
         // Mint used to fund validator identities for non-genesis accounts.
         // Verify we have enough lamports in the mint address to do those transfers.
         let mut required_mint_lamports = 0;

+ 19 - 0
program-binaries/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "solana-program-binaries"
+description = "Prebuilt SPL and Core BPF programs"
+version = { workspace = true }
+authors = { workspace = true }
+repository = { workspace = true }
+homepage = { workspace = true }
+license = { workspace = true }
+edition = { workspace = true }
+
+[dependencies]
+bincode = { workspace = true }
+serde = { workspace = true }
+solana-account = { workspace = true }
+solana-loader-v3-interface = { workspace = true, features = ["bincode", "serde"] }
+solana-pubkey = { workspace = true }
+solana-rent = { workspace = true }
+solana-sdk-ids = { workspace = true }
+spl-generic-token = { workspace = true }

+ 36 - 5
program-test/src/programs.rs → program-binaries/src/lib.rs

@@ -1,5 +1,7 @@
+#![allow(clippy::arithmetic_side_effects)]
+
 use {
-    solana_account::{Account, AccountSharedData},
+    solana_account::{Account, AccountSharedData, ReadableAccount},
     solana_loader_v3_interface::{get_program_data_address, state::UpgradeableLoaderState},
     solana_pubkey::Pubkey,
     solana_rent::Rent,
@@ -61,6 +63,11 @@ static CORE_BPF_PROGRAMS: &[(Pubkey, Option<Pubkey>, &[u8])] = &[
         None,
         include_bytes!("programs/core_bpf_feature_gate-0.0.1.so"),
     ),
+    (
+        solana_sdk_ids::stake::ID,
+        None,
+        include_bytes!("programs/core_bpf_stake-1.0.0.so"),
+    ),
     // Add more programs here post-migration...
 ];
 
@@ -75,7 +82,7 @@ fn bpf_loader_program_account(program_id: &Pubkey, elf: &[u8], rent: &Rent) -> (
             data: elf.to_vec(),
             owner: bpf_loader::id(),
             executable: true,
-            rent_epoch: 0,
+            rent_epoch: u64::MAX,
         },
     )
 }
@@ -86,7 +93,7 @@ fn bpf_loader_program_account(program_id: &Pubkey, elf: &[u8], rent: &Rent) -> (
 /// The second tuple is the program data account. It contains the program data
 /// address and an account with the program data - a valid BPF Loader Upgradeable
 /// program data account containing the ELF.
-pub(crate) fn bpf_loader_upgradeable_program_accounts(
+pub fn bpf_loader_upgradeable_program_accounts(
     program_id: &Pubkey,
     elf: &[u8],
     rent: &Rent,
@@ -104,7 +111,7 @@ pub(crate) fn bpf_loader_upgradeable_program_accounts(
             data,
             owner: bpf_loader_upgradeable::id(),
             executable: true,
-            rent_epoch: 0,
+            rent_epoch: u64::MAX,
         }
     };
     let programdata_account = {
@@ -121,7 +128,7 @@ pub(crate) fn bpf_loader_upgradeable_program_accounts(
             data,
             owner: bpf_loader_upgradeable::id(),
             executable: false,
-            rent_epoch: 0,
+            rent_epoch: u64::MAX,
         }
     };
     [
@@ -167,3 +174,27 @@ where
         })
         .collect()
 }
+
+pub fn by_id(program_id: &Pubkey, rent: &Rent) -> Option<Vec<(Pubkey, AccountSharedData)>> {
+    let programs = spl_programs(rent);
+    if let Some(i) = programs.iter().position(|(key, _)| key == program_id) {
+        let n = num_accounts(programs[i].1.owner());
+        return Some(programs.into_iter().skip(i).take(n).collect());
+    }
+
+    let programs = core_bpf_programs(rent, |_| true);
+    if let Some(i) = programs.iter().position(|(key, _)| key == program_id) {
+        let n = num_accounts(programs[i].1.owner());
+        return Some(programs.into_iter().skip(i).take(n).collect());
+    }
+
+    None
+}
+
+fn num_accounts(owner_id: &Pubkey) -> usize {
+    if *owner_id == bpf_loader_upgradeable::id() {
+        2
+    } else {
+        1
+    }
+}

+ 0 - 0
program-test/src/programs/core_bpf_address_lookup_table-3.0.0.so → program-binaries/src/programs/core_bpf_address_lookup_table-3.0.0.so


+ 0 - 0
program-test/src/programs/core_bpf_config-3.0.0.so → program-binaries/src/programs/core_bpf_config-3.0.0.so


+ 0 - 0
program-test/src/programs/core_bpf_feature_gate-0.0.1.so → program-binaries/src/programs/core_bpf_feature_gate-0.0.1.so


BIN
program-binaries/src/programs/core_bpf_stake-1.0.0.so


+ 0 - 0
program-test/src/programs/spl_associated_token_account-1.1.1.so → program-binaries/src/programs/spl_associated_token_account-1.1.1.so


+ 0 - 0
program-test/src/programs/spl_memo-1.0.0.so → program-binaries/src/programs/spl_memo-1.0.0.so


+ 0 - 0
program-test/src/programs/spl_memo-3.0.0.so → program-binaries/src/programs/spl_memo-3.0.0.so


+ 0 - 0
program-test/src/programs/spl_token-3.5.0.so → program-binaries/src/programs/spl_token-3.5.0.so


+ 0 - 0
program-test/src/programs/spl_token_2022-8.0.0.so → program-binaries/src/programs/spl_token_2022-8.0.0.so


+ 1 - 0
program-test/Cargo.toml

@@ -41,6 +41,7 @@ solana-message = { workspace = true }
 solana-msg = { workspace = true }
 solana-native-token = { workspace = true }
 solana-poh-config = { workspace = true }
+solana-program-binaries = { workspace = true }
 solana-program-entrypoint = { workspace = true }
 solana-program-error = { workspace = true }
 solana-program-runtime = { workspace = true }

+ 1 - 2
program-test/src/lib.rs

@@ -26,6 +26,7 @@ use {
     solana_keypair::Keypair,
     solana_native_token::LAMPORTS_PER_SOL,
     solana_poh_config::PohConfig,
+    solana_program_binaries as programs,
     solana_program_entrypoint::{deserialize, SUCCESS},
     solana_program_error::{ProgramError, ProgramResult},
     solana_program_runtime::{
@@ -77,8 +78,6 @@ pub use {
     solana_transaction_context::IndexOfAccount,
 };
 
-pub mod programs;
-
 /// Errors from the program test environment
 #[derive(Error, Debug, PartialEq, Eq)]
 pub enum ProgramTestError {

+ 3 - 2
program-test/tests/core_bpf.rs

@@ -26,12 +26,13 @@ async fn test_vended_core_bpf_programs() {
     assert_bpf_program(&context, &solana_sdk_ids::address_lookup_table::id()).await;
     assert_bpf_program(&context, &solana_sdk_ids::config::id()).await;
     assert_bpf_program(&context, &solana_sdk_ids::feature::id()).await;
+    assert_bpf_program(&context, &solana_sdk_ids::stake::id()).await;
 }
 
 #[tokio::test]
 async fn test_add_core_bpf_program_manually() {
-    // Core BPF program: Stake.
-    let program_id = solana_sdk_ids::stake::id();
+    // Core BPF program: Vote.
+    let program_id = solana_sdk_ids::vote::id();
 
     let mut program_test = ProgramTest::default();
     program_test.add_upgradeable_program_to_genesis("noop_program", &program_id);

+ 2 - 1
program-test/tests/spl.rs

@@ -1,7 +1,8 @@
 use {
     solana_instruction::{AccountMeta, Instruction},
     solana_keypair::Keypair,
-    solana_program_test::{programs::spl_programs, ProgramTest},
+    solana_program_binaries::spl_programs,
+    solana_program_test::ProgramTest,
     solana_pubkey::Pubkey,
     solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable},
     solana_signer::Signer,

+ 16 - 0
programs/sbf/Cargo.lock

@@ -7497,6 +7497,20 @@ dependencies = [
  "solana-sysvar-id",
 ]
 
+[[package]]
+name = "solana-program-binaries"
+version = "3.0.0"
+dependencies = [
+ "bincode",
+ "serde",
+ "solana-account",
+ "solana-loader-v3-interface",
+ "solana-pubkey",
+ "solana-rent",
+ "solana-sdk-ids",
+ "spl-generic-token",
+]
+
 [[package]]
 name = "solana-program-entrypoint"
 version = "3.1.0"
@@ -7621,6 +7635,7 @@ dependencies = [
  "solana-msg",
  "solana-native-token",
  "solana-poh-config",
+ "solana-program-binaries",
  "solana-program-entrypoint",
  "solana-program-error",
  "solana-program-runtime",
@@ -9507,6 +9522,7 @@ dependencies = [
  "solana-message",
  "solana-native-token",
  "solana-net-utils",
+ "solana-program-binaries",
  "solana-program-test",
  "solana-pubkey",
  "solana-rent",

+ 0 - 29
programs/sbf/tests/programs.rs

@@ -52,7 +52,6 @@ use {
     solana_sdk_ids::sysvar::{self as sysvar, clock},
     solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4},
     solana_signer::Signer,
-    solana_stake_interface as stake,
     solana_svm::{
         transaction_commit_result::{CommittedTransaction, TransactionCommitResult},
         transaction_processor::ExecutionRecordingConfig,
@@ -3666,34 +3665,6 @@ fn test_program_fees() {
     assert_eq!(pre_balance - post_balance, expected_prioritized_fee);
 }
 
-#[test]
-#[cfg(feature = "sbf_rust")]
-fn test_get_minimum_delegation() {
-    let GenesisConfigInfo {
-        genesis_config,
-        mint_keypair,
-        ..
-    } = create_genesis_config(100_123_456_789);
-
-    let bank = Bank::new_for_tests(&genesis_config);
-    let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
-    let mut bank_client = BankClient::new_shared(bank.clone());
-    let authority_keypair = Keypair::new();
-
-    let (_bank, program_id) = load_program_of_loader_v4(
-        &mut bank_client,
-        &bank_forks,
-        &mint_keypair,
-        &authority_keypair,
-        "solana_sbf_rust_get_minimum_delegation",
-    );
-
-    let account_metas = vec![AccountMeta::new_readonly(stake::program::id(), false)];
-    let instruction = Instruction::new_with_bytes(program_id, &[], account_metas);
-    let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
-    assert!(result.is_ok());
-}
-
 #[test]
 #[cfg(feature = "sbf_rust")]
 fn test_program_sbf_inner_instruction_alignment_checks() {

+ 0 - 34
programs/stake-tests/Cargo.toml

@@ -1,34 +0,0 @@
-# This package only exists to avoid circular dependencies during cargo publish:
-# solana-program-test <--> solana-stake-program
-
-[package]
-name = "solana-stake-program-tests"
-publish = false
-version = { workspace = true }
-authors = { workspace = true }
-repository = { workspace = true }
-homepage = { workspace = true }
-license = { workspace = true }
-edition = { workspace = true }
-
-[package.metadata.docs.rs]
-targets = ["x86_64-unknown-linux-gnu"]
-
-[dev-dependencies]
-agave-feature-set = { workspace = true }
-assert_matches = { workspace = true }
-bincode = { workspace = true }
-solana-account = { workspace = true }
-solana-instruction = { workspace = true }
-solana-keypair = { workspace = true }
-solana-program-error = { workspace = true }
-solana-program-test = { workspace = true }
-solana-pubkey = { workspace = true }
-solana-signer = { workspace = true }
-solana-stake-interface = { workspace = true }
-solana-system-interface = { workspace = true }
-solana-sysvar = { workspace = true }
-solana-transaction = { workspace = true }
-solana-transaction-error = { workspace = true }
-solana-vote-program = { workspace = true }
-test-case = { workspace = true }

+ 0 - 1222
programs/stake-tests/tests/test_move_stake_and_lamports.rs

@@ -1,1222 +0,0 @@
-#![allow(clippy::arithmetic_side_effects)]
-
-// NOTE this is temporarily ported from the bpf stake program repo so MoveStake and MoveLamports can be tested comprehensively
-// in the future we will either port *all* instruction tests from bpf stake program and remove existing stakeinstruction tests
-// or we will develop a text fixture system that allows fuzzing and obsoletes both existing test suites
-// in other words the utility functions in this file should not be broken out into modules or used elsewhere
-
-use {
-    agave_feature_set::stake_raise_minimum_delegation_to_1_sol,
-    solana_account::Account as SolanaAccount,
-    solana_instruction::Instruction,
-    solana_keypair::Keypair,
-    solana_program_error::{ProgramError, ProgramResult},
-    solana_program_test::*,
-    solana_pubkey::Pubkey,
-    solana_signer::{signers::Signers, Signer},
-    solana_stake_interface::{
-        self as stake,
-        error::StakeError,
-        instruction as ixn, program as stake_program,
-        stake_history::StakeHistory,
-        state::{Authorized, Lockup, Meta, Stake, StakeStateV2},
-    },
-    solana_system_interface::{instruction as system_instruction, program as system_program},
-    solana_sysvar::clock::Clock,
-    solana_transaction::Transaction,
-    solana_transaction_error::TransactionError,
-    solana_vote_program::{
-        self, vote_instruction,
-        vote_state::{VoteInit, VoteStateV3, VoteStateVersions},
-    },
-    test_case::test_matrix,
-};
-
-const NO_SIGNERS: &[Keypair] = &[];
-
-fn program_test() -> ProgramTest {
-    program_test_without_features(&[])
-}
-
-fn program_test_without_features(feature_ids: &[Pubkey]) -> ProgramTest {
-    let mut program_test = ProgramTest::default();
-    for feature_id in feature_ids {
-        program_test.deactivate_feature(*feature_id);
-    }
-
-    program_test
-}
-
-#[derive(Debug, PartialEq)]
-struct Accounts {
-    validator: Keypair,
-    voter: Keypair,
-    withdrawer: Keypair,
-    vote_account: Keypair,
-}
-
-impl Accounts {
-    async fn initialize(&self, context: &mut ProgramTestContext) {
-        let slot = context.genesis_config().epoch_schedule.first_normal_slot + 1;
-        context.warp_to_slot(slot).unwrap();
-
-        create_vote(
-            context,
-            &self.validator,
-            &self.voter.pubkey(),
-            &self.withdrawer.pubkey(),
-            &self.vote_account,
-        )
-        .await;
-    }
-}
-
-impl Default for Accounts {
-    fn default() -> Self {
-        Self {
-            validator: Keypair::new(),
-            voter: Keypair::new(),
-            withdrawer: Keypair::new(),
-            vote_account: Keypair::new(),
-        }
-    }
-}
-
-async fn create_vote(
-    context: &mut ProgramTestContext,
-    validator: &Keypair,
-    voter: &Pubkey,
-    withdrawer: &Pubkey,
-    vote_account: &Keypair,
-) {
-    let rent = context.banks_client.get_rent().await.unwrap();
-    let rent_voter = rent.minimum_balance(VoteStateV3::size_of());
-
-    let mut instructions = vec![system_instruction::create_account(
-        &context.payer.pubkey(),
-        &validator.pubkey(),
-        rent.minimum_balance(0),
-        0,
-        &system_program::id(),
-    )];
-    instructions.append(&mut vote_instruction::create_account_with_config(
-        &context.payer.pubkey(),
-        &vote_account.pubkey(),
-        &VoteInit {
-            node_pubkey: validator.pubkey(),
-            authorized_voter: *voter,
-            authorized_withdrawer: *withdrawer,
-            ..VoteInit::default()
-        },
-        rent_voter,
-        vote_instruction::CreateVoteAccountConfig {
-            space: VoteStateVersions::vote_state_size_of(true) as u64,
-            ..Default::default()
-        },
-    ));
-
-    let transaction = Transaction::new_signed_with_payer(
-        &instructions,
-        Some(&context.payer.pubkey()),
-        &[validator, vote_account, &context.payer],
-        context.last_blockhash,
-    );
-
-    // ignore errors for idempotency
-    let _ = context.banks_client.process_transaction(transaction).await;
-}
-
-async fn transfer(context: &mut ProgramTestContext, recipient: &Pubkey, amount: u64) {
-    let transaction = Transaction::new_signed_with_payer(
-        &[system_instruction::transfer(
-            &context.payer.pubkey(),
-            recipient,
-            amount,
-        )],
-        Some(&context.payer.pubkey()),
-        &[&context.payer],
-        context.last_blockhash,
-    );
-    context
-        .banks_client
-        .process_transaction(transaction)
-        .await
-        .unwrap();
-}
-
-async fn advance_epoch(context: &mut ProgramTestContext) {
-    refresh_blockhash(context).await;
-
-    let root_slot = context.banks_client.get_root_slot().await.unwrap();
-    let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
-    context.warp_to_slot(root_slot + slots_per_epoch).unwrap();
-}
-
-async fn refresh_blockhash(context: &mut ProgramTestContext) {
-    context.last_blockhash = context
-        .banks_client
-        .get_new_latest_blockhash(&context.last_blockhash)
-        .await
-        .unwrap();
-}
-
-async fn get_account(banks_client: &mut BanksClient, pubkey: &Pubkey) -> SolanaAccount {
-    banks_client
-        .get_account(*pubkey)
-        .await
-        .expect("client error")
-        .expect("account not found")
-}
-
-async fn get_stake_account(
-    banks_client: &mut BanksClient,
-    pubkey: &Pubkey,
-) -> (Meta, Option<Stake>, u64) {
-    let stake_account = get_account(banks_client, pubkey).await;
-    let lamports = stake_account.lamports;
-    match bincode::deserialize::<StakeStateV2>(&stake_account.data).unwrap() {
-        StakeStateV2::Initialized(meta) => (meta, None, lamports),
-        StakeStateV2::Stake(meta, stake, _) => (meta, Some(stake), lamports),
-        StakeStateV2::Uninitialized => panic!("panic: uninitialized"),
-        _ => unimplemented!(),
-    }
-}
-
-async fn get_stake_account_rent(banks_client: &mut BanksClient) -> u64 {
-    let rent = banks_client.get_rent().await.unwrap();
-    rent.minimum_balance(std::mem::size_of::<stake::state::StakeStateV2>())
-}
-
-async fn get_effective_stake(banks_client: &mut BanksClient, pubkey: &Pubkey) -> u64 {
-    let clock = banks_client.get_sysvar::<Clock>().await.unwrap();
-    let stake_history = banks_client.get_sysvar::<StakeHistory>().await.unwrap();
-    let stake_account = get_account(banks_client, pubkey).await;
-    match bincode::deserialize::<StakeStateV2>(&stake_account.data).unwrap() {
-        StakeStateV2::Stake(_, stake, _) => {
-            stake
-                .delegation
-                .stake_activating_and_deactivating(clock.epoch, &stake_history, Some(0))
-                .effective
-        }
-        _ => 0,
-    }
-}
-
-async fn get_minimum_delegation(context: &mut ProgramTestContext) -> u64 {
-    let transaction = Transaction::new_signed_with_payer(
-        &[stake::instruction::get_minimum_delegation()],
-        Some(&context.payer.pubkey()),
-        &[&context.payer],
-        context.last_blockhash,
-    );
-    let mut data = context
-        .banks_client
-        .simulate_transaction(transaction)
-        .await
-        .unwrap()
-        .simulation_details
-        .unwrap()
-        .return_data
-        .unwrap()
-        .data;
-    data.resize(8, 0);
-
-    data.try_into().map(u64::from_le_bytes).unwrap()
-}
-
-async fn create_blank_stake_account_from_keypair(
-    context: &mut ProgramTestContext,
-    stake: &Keypair,
-) -> Pubkey {
-    let lamports = get_stake_account_rent(&mut context.banks_client).await;
-
-    let transaction = Transaction::new_signed_with_payer(
-        &[system_instruction::create_account(
-            &context.payer.pubkey(),
-            &stake.pubkey(),
-            lamports,
-            StakeStateV2::size_of() as u64,
-            &stake_program::id(),
-        )],
-        Some(&context.payer.pubkey()),
-        &[&context.payer, stake],
-        context.last_blockhash,
-    );
-
-    context
-        .banks_client
-        .process_transaction(transaction)
-        .await
-        .unwrap();
-
-    stake.pubkey()
-}
-
-async fn process_instruction<T: Signers + ?Sized>(
-    context: &mut ProgramTestContext,
-    instruction: &Instruction,
-    additional_signers: &T,
-) -> ProgramResult {
-    let mut transaction =
-        Transaction::new_with_payer(&[instruction.clone()], Some(&context.payer.pubkey()));
-
-    transaction.partial_sign(&[&context.payer], context.last_blockhash);
-    transaction.sign(additional_signers, context.last_blockhash);
-
-    match context.banks_client.process_transaction(transaction).await {
-        Ok(_) => Ok(()),
-        Err(e) => {
-            // banks client error -> transaction error -> instruction error -> program error
-            match e.unwrap() {
-                TransactionError::InstructionError(_, e) => Err(e.try_into().unwrap()),
-                TransactionError::InsufficientFundsForRent { .. } => {
-                    Err(ProgramError::InsufficientFunds)
-                }
-                _ => panic!("couldnt convert {e:?} to ProgramError"),
-            }
-        }
-    }
-}
-
-async fn test_instruction_with_missing_signers(
-    context: &mut ProgramTestContext,
-    instruction: &Instruction,
-    additional_signers: &Vec<&Keypair>,
-) {
-    // remove every signer one by one and ensure we always fail
-    for i in 0..instruction.accounts.len() {
-        if instruction.accounts[i].is_signer {
-            let mut instruction = instruction.clone();
-            instruction.accounts[i].is_signer = false;
-            let reduced_signers: Vec<_> = additional_signers
-                .iter()
-                .filter(|s| s.pubkey() != instruction.accounts[i].pubkey)
-                .collect();
-
-            let e = process_instruction(context, &instruction, &reduced_signers)
-                .await
-                .unwrap_err();
-            assert_eq!(e, ProgramError::MissingRequiredSignature);
-        }
-    }
-
-    // now make sure the instruction succeeds
-    process_instruction(context, instruction, additional_signers)
-        .await
-        .unwrap();
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-enum StakeLifecycle {
-    Uninitialized = 0,
-    Initialized,
-    Activating,
-    Active,
-    Deactivating,
-    Deactive,
-}
-impl StakeLifecycle {
-    // (stake, staker, withdrawer)
-    async fn new_stake_account(
-        self,
-        context: &mut ProgramTestContext,
-        vote_account: &Pubkey,
-        staked_amount: u64,
-    ) -> (Keypair, Keypair, Keypair) {
-        let stake_keypair = Keypair::new();
-        let staker_keypair = Keypair::new();
-        let withdrawer_keypair = Keypair::new();
-
-        self.new_stake_account_fully_specified(
-            context,
-            vote_account,
-            staked_amount,
-            &stake_keypair,
-            &staker_keypair,
-            &withdrawer_keypair,
-            &Lockup::default(),
-        )
-        .await;
-
-        (stake_keypair, staker_keypair, withdrawer_keypair)
-    }
-
-    #[allow(clippy::too_many_arguments)]
-    async fn new_stake_account_fully_specified(
-        self,
-        context: &mut ProgramTestContext,
-        vote_account: &Pubkey,
-        staked_amount: u64,
-        stake_keypair: &Keypair,
-        staker_keypair: &Keypair,
-        withdrawer_keypair: &Keypair,
-        lockup: &Lockup,
-    ) {
-        let authorized = Authorized {
-            staker: staker_keypair.pubkey(),
-            withdrawer: withdrawer_keypair.pubkey(),
-        };
-
-        let stake = create_blank_stake_account_from_keypair(context, stake_keypair).await;
-        if staked_amount > 0 {
-            transfer(context, &stake, staked_amount).await;
-        }
-
-        if self >= StakeLifecycle::Initialized {
-            let instruction = ixn::initialize(&stake, &authorized, lockup);
-            process_instruction(context, &instruction, NO_SIGNERS)
-                .await
-                .unwrap();
-        }
-
-        if self >= StakeLifecycle::Activating {
-            let instruction = ixn::delegate_stake(&stake, &staker_keypair.pubkey(), vote_account);
-            process_instruction(context, &instruction, &vec![staker_keypair])
-                .await
-                .unwrap();
-        }
-
-        if self >= StakeLifecycle::Active {
-            advance_epoch(context).await;
-            assert_eq!(
-                get_effective_stake(&mut context.banks_client, &stake).await,
-                staked_amount,
-            );
-        }
-
-        if self >= StakeLifecycle::Deactivating {
-            let instruction = ixn::deactivate_stake(&stake, &staker_keypair.pubkey());
-            process_instruction(context, &instruction, &vec![staker_keypair])
-                .await
-                .unwrap();
-        }
-
-        if self == StakeLifecycle::Deactive {
-            advance_epoch(context).await;
-            assert_eq!(
-                get_effective_stake(&mut context.banks_client, &stake).await,
-                0,
-            );
-        }
-    }
-}
-
-#[test_matrix(
-    [program_test(),  program_test_without_features(&[stake_raise_minimum_delegation_to_1_sol::id()])],
-    [StakeLifecycle::Initialized, StakeLifecycle::Activating, StakeLifecycle::Active,
-     StakeLifecycle::Deactivating, StakeLifecycle::Deactive],
-    [StakeLifecycle::Initialized, StakeLifecycle::Activating, StakeLifecycle::Active,
-     StakeLifecycle::Deactivating, StakeLifecycle::Deactive],
-    [false, true],
-    [false, true]
-)]
-#[tokio::test]
-async fn test_move_stake(
-    program_test: ProgramTest,
-    move_source_type: StakeLifecycle,
-    move_dest_type: StakeLifecycle,
-    full_move: bool,
-    has_lockup: bool,
-) {
-    let mut context = program_test.start_with_context().await;
-    let accounts = Accounts::default();
-    accounts.initialize(&mut context).await;
-
-    let rent_exempt_reserve = get_stake_account_rent(&mut context.banks_client).await;
-    let minimum_delegation = get_minimum_delegation(&mut context).await;
-
-    // source has 2x minimum so we can easily test an unfunded destination
-    let source_staked_amount = minimum_delegation * 2;
-
-    // this is the amount of *staked* lamports for test checks
-    // destinations may have excess lamports but these are *never* activated by move
-    let dest_staked_amount = if move_dest_type == StakeLifecycle::Active {
-        minimum_delegation
-    } else {
-        0
-    };
-
-    // test with and without lockup. both of these cases pass, we test failures elsewhere
-    let lockup = if has_lockup {
-        let clock = context.banks_client.get_sysvar::<Clock>().await.unwrap();
-        let lockup = Lockup {
-            unix_timestamp: 0,
-            epoch: clock.epoch + 100,
-            custodian: Pubkey::new_unique(),
-        };
-
-        assert!(lockup.is_in_force(&clock, None));
-        lockup
-    } else {
-        Lockup::default()
-    };
-
-    // we put an extra minimum in every account, unstaked, to test that no new lamports activate
-    // name them here so our asserts are readable
-    let source_excess = minimum_delegation;
-    let dest_excess = minimum_delegation;
-
-    let move_source_keypair = Keypair::new();
-    let move_dest_keypair = Keypair::new();
-    let staker_keypair = Keypair::new();
-    let withdrawer_keypair = Keypair::new();
-
-    // create source stake
-    move_source_type
-        .new_stake_account_fully_specified(
-            &mut context,
-            &accounts.vote_account.pubkey(),
-            source_staked_amount,
-            &move_source_keypair,
-            &staker_keypair,
-            &withdrawer_keypair,
-            &lockup,
-        )
-        .await;
-    let move_source = move_source_keypair.pubkey();
-    let mut source_account = get_account(&mut context.banks_client, &move_source).await;
-    let mut source_stake_state: StakeStateV2 = bincode::deserialize(&source_account.data).unwrap();
-
-    // create dest stake with same authorities
-    move_dest_type
-        .new_stake_account_fully_specified(
-            &mut context,
-            &accounts.vote_account.pubkey(),
-            minimum_delegation,
-            &move_dest_keypair,
-            &staker_keypair,
-            &withdrawer_keypair,
-            &lockup,
-        )
-        .await;
-    let move_dest = move_dest_keypair.pubkey();
-
-    // true up source epoch if transient
-    if move_source_type == StakeLifecycle::Activating
-        || move_source_type == StakeLifecycle::Deactivating
-    {
-        let clock = context.banks_client.get_sysvar::<Clock>().await.unwrap();
-        if let StakeStateV2::Stake(_, ref mut stake, _) = &mut source_stake_state {
-            match move_source_type {
-                StakeLifecycle::Activating => stake.delegation.activation_epoch = clock.epoch,
-                StakeLifecycle::Deactivating => stake.delegation.deactivation_epoch = clock.epoch,
-                _ => (),
-            }
-        }
-
-        source_account.data = bincode::serialize(&source_stake_state).unwrap();
-        context.set_account(&move_source, &source_account.into());
-    }
-
-    // our inactive accounts have extra lamports, lets not let active feel left out
-    if move_dest_type == StakeLifecycle::Active {
-        transfer(&mut context, &move_dest, dest_excess).await;
-    }
-
-    // hey why not spread the love around to everyone
-    transfer(&mut context, &move_source, source_excess).await;
-
-    // alright first things first, clear out all the state failures
-    match (move_source_type, move_dest_type) {
-        // valid
-        (StakeLifecycle::Active, StakeLifecycle::Initialized)
-        | (StakeLifecycle::Active, StakeLifecycle::Active)
-        | (StakeLifecycle::Active, StakeLifecycle::Deactive) => (),
-        // invalid! get outta my test
-        _ => {
-            let instruction = ixn::move_stake(
-                &move_source,
-                &move_dest,
-                &staker_keypair.pubkey(),
-                if full_move {
-                    source_staked_amount
-                } else {
-                    minimum_delegation
-                },
-            );
-
-            // this is InvalidAccountData sometimes and Custom(5) sometimes but i dont care
-            process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-                .await
-                .unwrap_err();
-            return;
-        }
-    }
-
-    // the below checks are conceptually incoherent with a 1 lamport minimum
-    // the undershoot fails successfully (but because its a zero move, not because the destination ends underfunded)
-    // then the second one succeeds failedly (because its a full move, so the "underfunded" source is actually closed)
-    if minimum_delegation > 1 {
-        // source has 2x minimum (always 2 sol because these tests dont have featuresets)
-        // so first for inactive accounts lets undershoot and fail for underfunded dest
-        if move_dest_type != StakeLifecycle::Active {
-            let instruction = ixn::move_stake(
-                &move_source,
-                &move_dest,
-                &staker_keypair.pubkey(),
-                minimum_delegation - 1,
-            );
-
-            let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-                .await
-                .unwrap_err();
-            assert_eq!(e, ProgramError::InvalidArgument);
-        }
-
-        // now lets overshoot and fail for underfunded source
-        let instruction = ixn::move_stake(
-            &move_source,
-            &move_dest,
-            &staker_keypair.pubkey(),
-            minimum_delegation + 1,
-        );
-
-        let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-            .await
-            .unwrap_err();
-        assert_eq!(e, ProgramError::InvalidArgument);
-    }
-
-    // now we do it juuust right
-    let instruction = ixn::move_stake(
-        &move_source,
-        &move_dest,
-        &staker_keypair.pubkey(),
-        if full_move {
-            source_staked_amount
-        } else {
-            minimum_delegation
-        },
-    );
-
-    test_instruction_with_missing_signers(&mut context, &instruction, &vec![&staker_keypair]).await;
-
-    if full_move {
-        let (_, option_source_stake, source_lamports) =
-            get_stake_account(&mut context.banks_client, &move_source).await;
-
-        // source is deactivated and rent/excess stay behind
-        assert!(option_source_stake.is_none());
-        assert_eq!(source_lamports, source_excess + rent_exempt_reserve);
-
-        let (_, Some(dest_stake), dest_lamports) =
-            get_stake_account(&mut context.banks_client, &move_dest).await
-        else {
-            panic!("dest should be active")
-        };
-        let dest_effective_stake = get_effective_stake(&mut context.banks_client, &move_dest).await;
-
-        // dest captured the entire source delegation, kept its rent/excess, didnt activate its excess
-        assert_eq!(
-            dest_stake.delegation.stake,
-            source_staked_amount + dest_staked_amount
-        );
-        assert_eq!(dest_effective_stake, dest_stake.delegation.stake);
-        assert_eq!(
-            dest_lamports,
-            dest_effective_stake + dest_excess + rent_exempt_reserve
-        );
-    } else {
-        let (_, Some(source_stake), source_lamports) =
-            get_stake_account(&mut context.banks_client, &move_source).await
-        else {
-            panic!("source should be active")
-        };
-        let source_effective_stake =
-            get_effective_stake(&mut context.banks_client, &move_source).await;
-
-        // half of source delegation moved over, excess stayed behind
-        assert_eq!(source_stake.delegation.stake, source_staked_amount / 2);
-        assert_eq!(source_effective_stake, source_stake.delegation.stake);
-        assert_eq!(
-            source_lamports,
-            source_effective_stake + source_excess + rent_exempt_reserve
-        );
-
-        let (_, Some(dest_stake), dest_lamports) =
-            get_stake_account(&mut context.banks_client, &move_dest).await
-        else {
-            panic!("dest should be active")
-        };
-        let dest_effective_stake = get_effective_stake(&mut context.banks_client, &move_dest).await;
-
-        // dest mirrors our observations
-        assert_eq!(
-            dest_stake.delegation.stake,
-            source_staked_amount / 2 + dest_staked_amount
-        );
-        assert_eq!(dest_effective_stake, dest_stake.delegation.stake);
-        assert_eq!(
-            dest_lamports,
-            dest_effective_stake + dest_excess + rent_exempt_reserve
-        );
-    }
-}
-
-#[test_matrix(
-    [program_test(),  program_test_without_features(&[stake_raise_minimum_delegation_to_1_sol::id()])],
-    [StakeLifecycle::Initialized, StakeLifecycle::Activating, StakeLifecycle::Active,
-     StakeLifecycle::Deactivating, StakeLifecycle::Deactive],
-    [StakeLifecycle::Initialized, StakeLifecycle::Activating, StakeLifecycle::Active,
-     StakeLifecycle::Deactivating, StakeLifecycle::Deactive],
-    [false, true],
-    [false, true]
-)]
-#[tokio::test]
-async fn test_move_lamports(
-    program_test: ProgramTest,
-    move_source_type: StakeLifecycle,
-    move_dest_type: StakeLifecycle,
-    different_votes: bool,
-    has_lockup: bool,
-) {
-    let mut context = program_test.start_with_context().await;
-    let accounts = Accounts::default();
-    accounts.initialize(&mut context).await;
-
-    let rent_exempt_reserve = get_stake_account_rent(&mut context.banks_client).await;
-    let minimum_delegation = get_minimum_delegation(&mut context).await;
-
-    // put minimum in both accounts if theyre active
-    let source_staked_amount = if move_source_type == StakeLifecycle::Active {
-        minimum_delegation
-    } else {
-        0
-    };
-
-    let dest_staked_amount = if move_dest_type == StakeLifecycle::Active {
-        minimum_delegation
-    } else {
-        0
-    };
-
-    // test with and without lockup. both of these cases pass, we test failures elsewhere
-    let lockup = if has_lockup {
-        let clock = context.banks_client.get_sysvar::<Clock>().await.unwrap();
-        let lockup = Lockup {
-            unix_timestamp: 0,
-            epoch: clock.epoch + 100,
-            custodian: Pubkey::new_unique(),
-        };
-
-        assert!(lockup.is_in_force(&clock, None));
-        lockup
-    } else {
-        Lockup::default()
-    };
-
-    // we put an extra minimum in every account, unstaked, to test moving them
-    let source_excess = minimum_delegation;
-    let dest_excess = minimum_delegation;
-
-    let move_source_keypair = Keypair::new();
-    let move_dest_keypair = Keypair::new();
-    let staker_keypair = Keypair::new();
-    let withdrawer_keypair = Keypair::new();
-
-    // make a separate vote account if needed
-    let dest_vote_account = if different_votes {
-        let vote_account = Keypair::new();
-        create_vote(
-            &mut context,
-            &Keypair::new(),
-            &Pubkey::new_unique(),
-            &Pubkey::new_unique(),
-            &vote_account,
-        )
-        .await;
-
-        vote_account.pubkey()
-    } else {
-        accounts.vote_account.pubkey()
-    };
-
-    // create source stake
-    move_source_type
-        .new_stake_account_fully_specified(
-            &mut context,
-            &accounts.vote_account.pubkey(),
-            minimum_delegation,
-            &move_source_keypair,
-            &staker_keypair,
-            &withdrawer_keypair,
-            &lockup,
-        )
-        .await;
-    let move_source = move_source_keypair.pubkey();
-    let mut source_account = get_account(&mut context.banks_client, &move_source).await;
-    let mut source_stake_state: StakeStateV2 = bincode::deserialize(&source_account.data).unwrap();
-
-    // create dest stake with same authorities
-    move_dest_type
-        .new_stake_account_fully_specified(
-            &mut context,
-            &dest_vote_account,
-            minimum_delegation,
-            &move_dest_keypair,
-            &staker_keypair,
-            &withdrawer_keypair,
-            &lockup,
-        )
-        .await;
-    let move_dest = move_dest_keypair.pubkey();
-
-    // true up source epoch if transient
-    if move_source_type == StakeLifecycle::Activating
-        || move_source_type == StakeLifecycle::Deactivating
-    {
-        let clock = context.banks_client.get_sysvar::<Clock>().await.unwrap();
-        if let StakeStateV2::Stake(_, ref mut stake, _) = &mut source_stake_state {
-            match move_source_type {
-                StakeLifecycle::Activating => stake.delegation.activation_epoch = clock.epoch,
-                StakeLifecycle::Deactivating => stake.delegation.deactivation_epoch = clock.epoch,
-                _ => (),
-            }
-        }
-
-        source_account.data = bincode::serialize(&source_stake_state).unwrap();
-        context.set_account(&move_source, &source_account.into());
-    }
-
-    // if we activated the initial amount we need to top up with the test lamports
-    if move_source_type == StakeLifecycle::Active {
-        transfer(&mut context, &move_source, source_excess).await;
-    }
-    if move_dest_type == StakeLifecycle::Active {
-        transfer(&mut context, &move_dest, dest_excess).await;
-    }
-
-    // clear out state failures
-    if move_source_type == StakeLifecycle::Activating
-        || move_source_type == StakeLifecycle::Deactivating
-        || move_dest_type == StakeLifecycle::Deactivating
-    {
-        let instruction = ixn::move_lamports(
-            &move_source,
-            &move_dest,
-            &staker_keypair.pubkey(),
-            source_excess,
-        );
-
-        process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-            .await
-            .unwrap_err();
-        return;
-    }
-
-    // overshoot and fail for underfunded source
-    let instruction = ixn::move_lamports(
-        &move_source,
-        &move_dest,
-        &staker_keypair.pubkey(),
-        source_excess + 1,
-    );
-
-    let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-        .await
-        .unwrap_err();
-    assert_eq!(e, ProgramError::InvalidArgument);
-
-    let (_, _, before_source_lamports) =
-        get_stake_account(&mut context.banks_client, &move_source).await;
-    let (_, _, before_dest_lamports) =
-        get_stake_account(&mut context.banks_client, &move_dest).await;
-
-    // now properly move the full excess
-    let instruction = ixn::move_lamports(
-        &move_source,
-        &move_dest,
-        &staker_keypair.pubkey(),
-        source_excess,
-    );
-
-    test_instruction_with_missing_signers(&mut context, &instruction, &vec![&staker_keypair]).await;
-
-    let (_, _, after_source_lamports) =
-        get_stake_account(&mut context.banks_client, &move_source).await;
-    let source_effective_stake = get_effective_stake(&mut context.banks_client, &move_source).await;
-
-    // source activation didnt change
-    assert_eq!(source_effective_stake, source_staked_amount);
-
-    // source lamports are right
-    assert_eq!(
-        after_source_lamports,
-        before_source_lamports - minimum_delegation
-    );
-    assert_eq!(
-        after_source_lamports,
-        source_effective_stake + rent_exempt_reserve
-    );
-
-    let (_, _, after_dest_lamports) =
-        get_stake_account(&mut context.banks_client, &move_dest).await;
-    let dest_effective_stake = get_effective_stake(&mut context.banks_client, &move_dest).await;
-
-    // dest activation didnt change
-    assert_eq!(dest_effective_stake, dest_staked_amount);
-
-    // dest lamports are right
-    assert_eq!(
-        after_dest_lamports,
-        before_dest_lamports + minimum_delegation
-    );
-    assert_eq!(
-        after_dest_lamports,
-        dest_effective_stake + rent_exempt_reserve + source_excess + dest_excess
-    );
-}
-
-#[test_matrix(
-    [program_test(),  program_test_without_features(&[stake_raise_minimum_delegation_to_1_sol::id()])],
-    [(StakeLifecycle::Active, StakeLifecycle::Uninitialized),
-     (StakeLifecycle::Uninitialized, StakeLifecycle::Initialized),
-     (StakeLifecycle::Uninitialized, StakeLifecycle::Uninitialized)],
-    [false, true]
-)]
-#[tokio::test]
-async fn test_move_uninitialized_fail(
-    program_test: ProgramTest,
-    move_types: (StakeLifecycle, StakeLifecycle),
-    move_lamports: bool,
-) {
-    let mut context = program_test.start_with_context().await;
-    let accounts = Accounts::default();
-    accounts.initialize(&mut context).await;
-
-    let minimum_delegation = get_minimum_delegation(&mut context).await;
-    let source_staked_amount = minimum_delegation * 2;
-
-    let (move_source_type, move_dest_type) = move_types;
-
-    let (move_source_keypair, staker_keypair, withdrawer_keypair) = move_source_type
-        .new_stake_account(
-            &mut context,
-            &accounts.vote_account.pubkey(),
-            source_staked_amount,
-        )
-        .await;
-    let move_source = move_source_keypair.pubkey();
-
-    let move_dest_keypair = Keypair::new();
-    move_dest_type
-        .new_stake_account_fully_specified(
-            &mut context,
-            &accounts.vote_account.pubkey(),
-            0,
-            &move_dest_keypair,
-            &staker_keypair,
-            &withdrawer_keypair,
-            &Lockup::default(),
-        )
-        .await;
-    let move_dest = move_dest_keypair.pubkey();
-
-    let source_signer = if move_source_type == StakeLifecycle::Uninitialized {
-        &move_source_keypair
-    } else {
-        &staker_keypair
-    };
-
-    let instruction = if move_lamports {
-        ixn::move_lamports(
-            &move_source,
-            &move_dest,
-            &source_signer.pubkey(),
-            minimum_delegation,
-        )
-    } else {
-        ixn::move_stake(
-            &move_source,
-            &move_dest,
-            &source_signer.pubkey(),
-            minimum_delegation,
-        )
-    };
-
-    let e = process_instruction(&mut context, &instruction, &vec![source_signer])
-        .await
-        .unwrap_err();
-    assert_eq!(e, ProgramError::InvalidAccountData);
-}
-
-#[test_matrix(
-    [program_test(),  program_test_without_features(&[stake_raise_minimum_delegation_to_1_sol::id()])],
-    [StakeLifecycle::Initialized, StakeLifecycle::Active, StakeLifecycle::Deactive],
-    [StakeLifecycle::Initialized, StakeLifecycle::Activating, StakeLifecycle::Active, StakeLifecycle::Deactive],
-    [false, true]
-)]
-#[tokio::test]
-async fn test_move_general_fail(
-    program_test: ProgramTest,
-    move_source_type: StakeLifecycle,
-    move_dest_type: StakeLifecycle,
-    move_lamports: bool,
-) {
-    // the test_matrix includes all valid source/dest combinations for MoveLamports
-    // we dont test invalid combinations because they would fail regardless of the fail cases we test here
-    // valid source/dest for MoveStake are a strict subset of MoveLamports
-    // source must be active, and dest must be active or inactive. so we skip the additional invalid MoveStake cases
-    if !move_lamports
-        && (move_source_type != StakeLifecycle::Active
-            || move_dest_type == StakeLifecycle::Activating)
-    {
-        return;
-    }
-
-    let mut context = program_test.start_with_context().await;
-    let accounts = Accounts::default();
-    accounts.initialize(&mut context).await;
-
-    let minimum_delegation = get_minimum_delegation(&mut context).await;
-    let source_staked_amount = minimum_delegation * 2;
-
-    let in_force_lockup = {
-        let clock = context.banks_client.get_sysvar::<Clock>().await.unwrap();
-        Lockup {
-            unix_timestamp: 0,
-            epoch: clock.epoch + 1_000_000,
-            custodian: Pubkey::new_unique(),
-        }
-    };
-
-    let mk_ixn = if move_lamports {
-        ixn::move_lamports
-    } else {
-        ixn::move_stake
-    };
-
-    // we can reuse source but will need a lot of dest
-    let (move_source_keypair, staker_keypair, withdrawer_keypair) = move_source_type
-        .new_stake_account(
-            &mut context,
-            &accounts.vote_account.pubkey(),
-            source_staked_amount,
-        )
-        .await;
-    let move_source = move_source_keypair.pubkey();
-    transfer(&mut context, &move_source, minimum_delegation).await;
-
-    // self-move fails
-    // NOTE this error type is an artifact of the native program interface
-    // when we move to bpf, it should actually hit the processor error
-    let instruction = mk_ixn(
-        &move_source,
-        &move_source,
-        &staker_keypair.pubkey(),
-        minimum_delegation,
-    );
-    let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-        .await
-        .unwrap_err();
-    assert_eq!(e, ProgramError::AccountBorrowFailed);
-
-    // first we make a "normal" move dest
-    {
-        let move_dest_keypair = Keypair::new();
-        move_dest_type
-            .new_stake_account_fully_specified(
-                &mut context,
-                &accounts.vote_account.pubkey(),
-                minimum_delegation,
-                &move_dest_keypair,
-                &staker_keypair,
-                &withdrawer_keypair,
-                &Lockup::default(),
-            )
-            .await;
-        let move_dest = move_dest_keypair.pubkey();
-
-        // zero move fails
-        let instruction = mk_ixn(&move_source, &move_dest, &staker_keypair.pubkey(), 0);
-        let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-            .await
-            .unwrap_err();
-        assert_eq!(e, ProgramError::InvalidArgument);
-
-        // sign with withdrawer fails
-        let instruction = mk_ixn(
-            &move_source,
-            &move_dest,
-            &withdrawer_keypair.pubkey(),
-            minimum_delegation,
-        );
-        let e = process_instruction(&mut context, &instruction, &vec![&withdrawer_keypair])
-            .await
-            .unwrap_err();
-        assert_eq!(e, ProgramError::MissingRequiredSignature);
-
-        // good place to test source lockup
-        let move_locked_source_keypair = Keypair::new();
-        move_source_type
-            .new_stake_account_fully_specified(
-                &mut context,
-                &accounts.vote_account.pubkey(),
-                source_staked_amount,
-                &move_locked_source_keypair,
-                &staker_keypair,
-                &withdrawer_keypair,
-                &in_force_lockup,
-            )
-            .await;
-        let move_locked_source = move_locked_source_keypair.pubkey();
-        transfer(&mut context, &move_locked_source, minimum_delegation).await;
-
-        let instruction = mk_ixn(
-            &move_locked_source,
-            &move_dest,
-            &staker_keypair.pubkey(),
-            minimum_delegation,
-        );
-        let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-            .await
-            .unwrap_err();
-        assert_eq!(e, StakeError::MergeMismatch.into());
-    }
-
-    // staker mismatch
-    {
-        let move_dest_keypair = Keypair::new();
-        let throwaway = Keypair::new();
-        move_dest_type
-            .new_stake_account_fully_specified(
-                &mut context,
-                &accounts.vote_account.pubkey(),
-                minimum_delegation,
-                &move_dest_keypair,
-                &throwaway,
-                &withdrawer_keypair,
-                &Lockup::default(),
-            )
-            .await;
-        let move_dest = move_dest_keypair.pubkey();
-
-        let instruction = mk_ixn(
-            &move_source,
-            &move_dest,
-            &staker_keypair.pubkey(),
-            minimum_delegation,
-        );
-        let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-            .await
-            .unwrap_err();
-        assert_eq!(e, StakeError::MergeMismatch.into());
-
-        let instruction = mk_ixn(
-            &move_source,
-            &move_dest,
-            &throwaway.pubkey(),
-            minimum_delegation,
-        );
-        let e = process_instruction(&mut context, &instruction, &vec![&throwaway])
-            .await
-            .unwrap_err();
-        assert_eq!(e, ProgramError::MissingRequiredSignature);
-    }
-
-    // withdrawer mismatch
-    {
-        let move_dest_keypair = Keypair::new();
-        let throwaway = Keypair::new();
-        move_dest_type
-            .new_stake_account_fully_specified(
-                &mut context,
-                &accounts.vote_account.pubkey(),
-                minimum_delegation,
-                &move_dest_keypair,
-                &staker_keypair,
-                &throwaway,
-                &Lockup::default(),
-            )
-            .await;
-        let move_dest = move_dest_keypair.pubkey();
-
-        let instruction = mk_ixn(
-            &move_source,
-            &move_dest,
-            &staker_keypair.pubkey(),
-            minimum_delegation,
-        );
-        let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-            .await
-            .unwrap_err();
-        assert_eq!(e, StakeError::MergeMismatch.into());
-
-        let instruction = mk_ixn(
-            &move_source,
-            &move_dest,
-            &throwaway.pubkey(),
-            minimum_delegation,
-        );
-        let e = process_instruction(&mut context, &instruction, &vec![&throwaway])
-            .await
-            .unwrap_err();
-        assert_eq!(e, ProgramError::MissingRequiredSignature);
-    }
-
-    // dest lockup
-    {
-        let move_dest_keypair = Keypair::new();
-        move_dest_type
-            .new_stake_account_fully_specified(
-                &mut context,
-                &accounts.vote_account.pubkey(),
-                minimum_delegation,
-                &move_dest_keypair,
-                &staker_keypair,
-                &withdrawer_keypair,
-                &in_force_lockup,
-            )
-            .await;
-        let move_dest = move_dest_keypair.pubkey();
-
-        let instruction = mk_ixn(
-            &move_source,
-            &move_dest,
-            &staker_keypair.pubkey(),
-            minimum_delegation,
-        );
-        let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-            .await
-            .unwrap_err();
-        assert_eq!(e, StakeError::MergeMismatch.into());
-    }
-
-    // lastly we test different vote accounts for move_stake
-    if !move_lamports && move_dest_type == StakeLifecycle::Active {
-        let dest_vote_account_keypair = Keypair::new();
-        create_vote(
-            &mut context,
-            &Keypair::new(),
-            &Pubkey::new_unique(),
-            &Pubkey::new_unique(),
-            &dest_vote_account_keypair,
-        )
-        .await;
-
-        let move_dest_keypair = Keypair::new();
-        move_dest_type
-            .new_stake_account_fully_specified(
-                &mut context,
-                &dest_vote_account_keypair.pubkey(),
-                minimum_delegation,
-                &move_dest_keypair,
-                &staker_keypair,
-                &withdrawer_keypair,
-                &Lockup::default(),
-            )
-            .await;
-        let move_dest = move_dest_keypair.pubkey();
-
-        let instruction = mk_ixn(
-            &move_source,
-            &move_dest,
-            &staker_keypair.pubkey(),
-            minimum_delegation,
-        );
-        let e = process_instruction(&mut context, &instruction, &vec![&staker_keypair])
-            .await
-            .unwrap_err();
-        assert_eq!(e, StakeError::VoteAddressMismatch.into());
-    }
-}

+ 40 - 53
rpc/src/rpc_pubsub.rs

@@ -643,22 +643,20 @@ mod tests {
             },
         },
         solana_signer::Signer,
-        solana_stake_interface::{
-            self as stake, instruction as stake_instruction,
-            state::{Authorized, Lockup, StakeAuthorize, StakeStateV2},
-        },
-        solana_stake_program::stake_state,
         solana_system_interface::{instruction as system_instruction, program as system_program},
         solana_system_transaction as system_transaction,
         solana_transaction::Transaction,
         solana_vote::vote_transaction::VoteTransaction,
-        solana_vote_program::vote_state::Vote,
+        solana_vote_interface::{
+            instruction::{self as vote_instruction, CreateVoteAccountConfig},
+            program as vote_program,
+            state::{Vote, VoteInit, VoteStateVersions},
+        },
         std::{
             sync::{
                 atomic::{AtomicBool, AtomicU64},
                 RwLock,
             },
-            thread::sleep,
             time::Duration,
         },
     };
@@ -875,11 +873,10 @@ mod tests {
         genesis_config.rent = Rent::default();
         activate_all_features(&mut genesis_config);
 
-        let new_stake_authority = solana_pubkey::new_rand();
-        let stake_authority = Keypair::new();
+        let validator = Keypair::new();
+        let voter = Keypair::new();
         let from = Keypair::new();
-        let stake_account = Keypair::new();
-        let stake_program_id = stake::program::id();
+        let vote_account = Keypair::new();
         let bank = Bank::new_for_tests(&genesis_config);
         let blockhash = bank.last_blockhash();
         let bank_forks = BankForks::new_rw_arc(bank);
@@ -902,7 +899,7 @@ mod tests {
         let encoding = UiAccountEncoding::Base64;
 
         rpc.account_subscribe(
-            stake_account.pubkey().to_string(),
+            vote_account.pubkey().to_string(),
             Some(RpcAccountInfoConfig {
                 commitment: Some(CommitmentConfig::processed()),
                 encoding: Some(encoding),
@@ -913,24 +910,42 @@ mod tests {
         .unwrap();
         rpc.block_until_processed(&rpc_subscriptions);
 
-        let balance = {
+        let (validator_balance, vote_balance) = {
             let bank = bank_forks.read().unwrap().working_bank();
             let rent = &bank.rent_collector().rent;
-            rent.minimum_balance(StakeStateV2::size_of())
+            (
+                rent.minimum_balance(0),
+                rent.minimum_balance(VoteStateVersions::vote_state_size_of(true)),
+            )
         };
+        let balance = validator_balance + vote_balance;
 
         let tx = system_transaction::transfer(&alice, &from.pubkey(), balance, blockhash);
         process_transaction_and_notify(&bank_forks, &tx, &rpc_subscriptions, 1).unwrap();
-        let authorized = Authorized::auto(&stake_authority.pubkey());
-        let ixs = stake_instruction::create_account(
+        let mut ixs = vec![system_instruction::create_account(
             &from.pubkey(),
-            &stake_account.pubkey(),
-            &authorized,
-            &Lockup::default(),
-            balance,
-        );
+            &validator.pubkey(),
+            validator_balance,
+            0,
+            &system_program::id(),
+        )];
+        ixs.append(&mut vote_instruction::create_account_with_config(
+            &from.pubkey(),
+            &vote_account.pubkey(),
+            &VoteInit {
+                node_pubkey: validator.pubkey(),
+                authorized_voter: voter.pubkey(),
+                authorized_withdrawer: Pubkey::new_unique(),
+                ..VoteInit::default()
+            },
+            vote_balance,
+            CreateVoteAccountConfig {
+                space: VoteStateVersions::vote_state_size_of(true) as u64,
+                ..CreateVoteAccountConfig::default()
+            },
+        ));
         let message = Message::new(&ixs, Some(&from.pubkey()));
-        let tx = Transaction::new(&[&from, &stake_account], message, blockhash);
+        let tx = Transaction::new(&[&from, &vote_account, &validator], message, blockhash);
         process_transaction_and_notify(&bank_forks, &tx, &rpc_subscriptions, 1).unwrap();
 
         // Test signature confirmation notification #1
@@ -939,7 +954,7 @@ mod tests {
             .unwrap()
             .get(1)
             .unwrap()
-            .get_account(&stake_account.pubkey())
+            .get_account(&vote_account.pubkey())
             .unwrap();
         let expected_data = account.data();
         let expected = json!({
@@ -949,8 +964,8 @@ mod tests {
                "result": {
                    "context": { "slot": 1 },
                    "value": {
-                       "owner": stake_program_id.to_string(),
-                       "lamports": balance,
+                       "owner": vote_program::id().to_string(),
+                       "lamports": vote_balance,
                        "data": [BASE64_STANDARD.encode(expected_data), encoding],
                        "executable": false,
                        "rentEpoch": u64::MAX,
@@ -966,34 +981,6 @@ mod tests {
             expected,
             serde_json::from_str::<serde_json::Value>(&response).unwrap(),
         );
-
-        let balance = {
-            let bank = bank_forks.read().unwrap().working_bank();
-            let rent = &bank.rent_collector().rent;
-            rent.minimum_balance(0)
-        };
-        let tx =
-            system_transaction::transfer(&alice, &stake_authority.pubkey(), balance, blockhash);
-        process_transaction_and_notify(&bank_forks, &tx, &rpc_subscriptions, 1).unwrap();
-        sleep(Duration::from_millis(200));
-        let ix = stake_instruction::authorize(
-            &stake_account.pubkey(),
-            &stake_authority.pubkey(),
-            &new_stake_authority,
-            StakeAuthorize::Staker,
-            None,
-        );
-        let message = Message::new(&[ix], Some(&stake_authority.pubkey()));
-        let tx = Transaction::new(&[&stake_authority], message, blockhash);
-        process_transaction_and_notify(&bank_forks, &tx, &rpc_subscriptions, 1).unwrap();
-        sleep(Duration::from_millis(200));
-
-        let bank = bank_forks.read().unwrap()[1].clone();
-        let account = bank.get_account(&stake_account.pubkey()).unwrap();
-        assert_eq!(
-            stake_state::authorized_from(&account).unwrap().staker,
-            new_stake_authority
-        );
     }
 
     #[test]

+ 1 - 0
runtime/Cargo.toml

@@ -193,6 +193,7 @@ solana-accounts-db = { workspace = true, features = ["dev-context-only-utils"] }
 solana-builtins = { workspace = true, features = ["dev-context-only-utils"] }
 solana-instruction-error = { workspace = true }
 solana-logger = { workspace = true }
+solana-program-binaries = { workspace = true }
 # See order-crates-for-publishing.py for using this unusual `path = "."`
 solana-runtime = { path = ".", features = ["dev-context-only-utils"] }
 solana-runtime-transaction = { workspace = true, features = [

+ 2 - 3
runtime/src/bank/builtin_programs.rs

@@ -148,9 +148,8 @@ mod tests_core_bpf_migration {
     // See `solana_svm::account_loader::load_transaction_accounts`.
     #[test_case(TestPrototype::Builtin(&BUILTINS[0]); "system")]
     #[test_case(TestPrototype::Builtin(&BUILTINS[1]); "vote")]
-    #[test_case(TestPrototype::Builtin(&BUILTINS[2]); "stake")]
-    #[test_case(TestPrototype::Builtin(&BUILTINS[3]); "bpf_loader_deprecated")]
-    #[test_case(TestPrototype::Builtin(&BUILTINS[4]); "bpf_loader")]
+    #[test_case(TestPrototype::Builtin(&BUILTINS[2]); "bpf_loader_deprecated")]
+    #[test_case(TestPrototype::Builtin(&BUILTINS[3]); "bpf_loader")]
     fn test_core_bpf_migration(prototype: TestPrototype) {
         let (mut genesis_config, mint_keypair) =
             create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);

+ 0 - 1
runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs

@@ -106,7 +106,6 @@ mod tests {
     #[test_case(solana_sdk_ids::bpf_loader_deprecated::id(), None)]
     #[test_case(solana_sdk_ids::bpf_loader_upgradeable::id(), None)]
     #[test_case(solana_compute_budget_interface::id(), None)]
-    #[test_case(solana_stake_interface::program::id(), None)]
     #[test_case(solana_system_interface::program::id(), None)]
     #[test_case(solana_vote_interface::program::id(), None)]
     #[test_case(

+ 3 - 38
runtime/src/bank/partitioned_epoch_rewards/mod.rs

@@ -321,9 +321,8 @@ mod tests {
         solana_native_token::LAMPORTS_PER_SOL,
         solana_reward_info::RewardType,
         solana_signer::Signer,
-        solana_stake_interface::{error::StakeError, state::StakeStateV2},
+        solana_stake_interface::state::StakeStateV2,
         solana_system_transaction as system_transaction,
-        solana_transaction::Transaction,
         solana_vote::vote_transaction,
         solana_vote_interface::state::{VoteStateVersions, MAX_LOCKOUT_HISTORY},
         solana_vote_program::vote_state::{self, TowerSync},
@@ -858,13 +857,9 @@ mod tests {
         }
     }
 
-    /// Test that program execution that attempts to mutate a stake account
-    /// incorrectly should fail during reward period. A credit should succeed,
-    /// but a withdrawal should fail.
+    /// Test that lamports can be sent to stake accounts regardless of rewards period.
     #[test]
-    fn test_program_execution_restricted_for_stake_account_in_reward_period() {
-        use solana_transaction_error::TransactionError::InstructionError;
-
+    fn test_rewards_period_system_transfer() {
         let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
         let validator_keypairs = vec![&validator_vote_keypairs];
         let GenesisConfigInfo {
@@ -939,36 +934,6 @@ mod tests {
             // Credits should always succeed
             assert!(system_result.is_ok());
 
-            // Attempt to withdraw from new stake account to the mint
-            let stake_ix = solana_stake_interface::instruction::withdraw(
-                &new_stake_address,
-                &new_stake_address,
-                &mint_keypair.pubkey(),
-                transfer_amount,
-                None,
-            );
-            let stake_tx = Transaction::new_signed_with_payer(
-                &[stake_ix],
-                Some(&mint_keypair.pubkey()),
-                &[&mint_keypair, &new_stake_signer],
-                bank.last_blockhash(),
-            );
-            let stake_result = bank.process_transaction(&stake_tx);
-
-            if slot == num_slots_in_epoch {
-                // When the bank is at the beginning of the new epoch, i.e. slot
-                // 32, StakeError::EpochRewardsActive should be thrown for
-                // actions like StakeInstruction::Withdraw
-                assert_eq!(
-                    stake_result,
-                    Err(InstructionError(0, StakeError::EpochRewardsActive.into()))
-                );
-            } else {
-                // When the bank is outside of reward interval, the withdraw
-                // transaction should not be affected and will succeed.
-                assert!(stake_result.is_ok());
-            }
-
             // Push a dummy blockhash, so that the latest_blockhash() for the transfer transaction in each
             // iteration are different. Otherwise, all those transactions will be the same, and will not be
             // executed by the bank except the first one.

+ 27 - 35
runtime/src/bank/tests.rs

@@ -117,8 +117,8 @@ use {
     solana_vote_program::{
         vote_instruction,
         vote_state::{
-            self, create_account_with_authorized, BlockTimestamp, VoteInit, VoteStateV3,
-            VoteStateVersions, MAX_LOCKOUT_HISTORY,
+            self, create_account_with_authorized, BlockTimestamp, VoteAuthorize, VoteInit,
+            VoteStateV3, VoteStateVersions, MAX_LOCKOUT_HISTORY,
         },
     },
     spl_generic_token::token,
@@ -3094,7 +3094,18 @@ fn test_bank_cloned_stake_delegations() {
         123_000_000_000,
     );
     genesis_config.rent = Rent::default();
+
+    for (pubkey, account) in
+        solana_program_binaries::by_id(&solana_stake_program::id(), &genesis_config.rent)
+            .unwrap()
+            .into_iter()
+    {
+        genesis_config.add_account(pubkey, account);
+    }
+
     let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
+    bank.squash();
+    let bank = Bank::new_from_parent(bank, &Pubkey::new_unique(), 1);
 
     let stake_delegations = bank.stakes_cache.stakes().stake_delegations().clone();
     assert_eq!(stake_delegations.len(), 1); // bootstrap validator has
@@ -5174,7 +5185,7 @@ fn test_bank_hash_consistency() {
             assert_eq!(bank.epoch(), 0);
             assert_eq!(
                 bank.hash().to_string(),
-                "AyXhbqmPsC46x7MHAuW89pQcNZVrUZnAND6ABWJ24svx",
+                "EzyLJJki4ALhQAq5wbmiNctDhytQckGJRXnk9APKXv7r",
             );
         }
 
@@ -5182,14 +5193,14 @@ fn test_bank_hash_consistency() {
             assert_eq!(bank.epoch(), 1);
             assert_eq!(
                 bank.hash().to_string(),
-                "ApbSYzbXgNBobjzp8ytimvVsMBUxtuJR9nFieePdpwj3"
+                "6h1KzSuTW6MwkgjtEbrv6AyUZ2NHtSxCQi8epjHDFYh8"
             );
         }
         if bank.slot == 128 {
             assert_eq!(bank.epoch(), 2);
             assert_eq!(
                 bank.hash().to_string(),
-                "FxaFn1Dj7fetY1SXWWi6DyEYidoiDLZexe3hM1tNvkwJ"
+                "4GX3883TVK7SQfbPUHem4HXcqdHU2DZVAB6yEXspn2qe"
             );
             break;
         }
@@ -5407,7 +5418,7 @@ fn test_shrink_candidate_slots_cached() {
     // No more slots should be shrunk
     assert_eq!(bank2.shrink_candidate_slots(), 0);
     // alive_counts represents the count of alive accounts in the three slots 0,1,2
-    assert_eq!(alive_counts, vec![13, 1, 6]);
+    assert_eq!(alive_counts, vec![12, 1, 6]);
 }
 
 #[test]
@@ -8182,7 +8193,6 @@ fn test_vote_epoch_panic() {
     let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
 
     let vote_keypair = keypair_from_seed(&[1u8; 32]).unwrap();
-    let stake_keypair = keypair_from_seed(&[2u8; 32]).unwrap();
 
     let mut setup_ixs = Vec::new();
     setup_ixs.extend(vote_instruction::create_account_with_config(
@@ -8200,14 +8210,6 @@ fn test_vote_epoch_panic() {
             ..vote_instruction::CreateVoteAccountConfig::default()
         },
     ));
-    setup_ixs.extend(stake_instruction::create_account_and_delegate_stake(
-        &mint_keypair.pubkey(),
-        &stake_keypair.pubkey(),
-        &vote_keypair.pubkey(),
-        &Authorized::auto(&mint_keypair.pubkey()),
-        &Lockup::default(),
-        1_000_000_000_000,
-    ));
     setup_ixs.push(vote_instruction::withdraw(
         &vote_keypair.pubkey(),
         &mint_keypair.pubkey(),
@@ -8221,7 +8223,7 @@ fn test_vote_epoch_panic() {
     ));
 
     let result = bank.process_transaction(&Transaction::new(
-        &[&mint_keypair, &vote_keypair, &stake_keypair],
+        &[&mint_keypair, &vote_keypair],
         Message::new(&setup_ixs, Some(&mint_keypair.pubkey())),
         bank.last_blockhash(),
     ));
@@ -9762,9 +9764,9 @@ fn test_rent_state_changes_sysvars() {
     } = create_genesis_config_with_leader(100 * LAMPORTS_PER_SOL, &Pubkey::new_unique(), 42);
     genesis_config.rent = Rent::default();
 
-    let validator_pubkey = solana_pubkey::new_rand();
+    let validator_pubkey = Pubkey::new_unique();
     let validator_stake_lamports = LAMPORTS_PER_SOL;
-    let validator_staking_keypair = Keypair::new();
+    let validator_vote_account_pubkey = Pubkey::new_unique();
     let validator_voting_keypair = Keypair::new();
 
     let validator_vote_account = vote_state::create_account(
@@ -9774,14 +9776,6 @@ fn test_rent_state_changes_sysvars() {
         validator_stake_lamports,
     );
 
-    let validator_stake_account = stake_state::create_account(
-        &validator_staking_keypair.pubkey(),
-        &validator_voting_keypair.pubkey(),
-        &validator_vote_account,
-        &genesis_config.rent,
-        validator_stake_lamports,
-    );
-
     genesis_config.accounts.insert(
         validator_pubkey,
         Account::new(
@@ -9791,11 +9785,7 @@ fn test_rent_state_changes_sysvars() {
         ),
     );
     genesis_config.accounts.insert(
-        validator_staking_keypair.pubkey(),
-        Account::from(validator_stake_account),
-    );
-    genesis_config.accounts.insert(
-        validator_voting_keypair.pubkey(),
+        validator_vote_account_pubkey,
         Account::from(validator_vote_account),
     );
 
@@ -9803,12 +9793,14 @@ fn test_rent_state_changes_sysvars() {
 
     // Ensure transactions with sysvars succeed, even though sysvars appear RentPaying by balance
     let tx = Transaction::new_signed_with_payer(
-        &[stake_instruction::deactivate_stake(
-            &validator_staking_keypair.pubkey(),
-            &validator_staking_keypair.pubkey(),
+        &[vote_instruction::authorize(
+            &validator_vote_account_pubkey,
+            &validator_voting_keypair.pubkey(),
+            &Pubkey::new_unique(),
+            VoteAuthorize::Voter,
         )],
         Some(&mint_keypair.pubkey()),
-        &[&mint_keypair, &validator_staking_keypair],
+        &[&mint_keypair, &validator_voting_keypair],
         bank.last_blockhash(),
     );
     let result = bank.process_transaction(&tx);

+ 1 - 1
runtime/src/genesis_utils.rs

@@ -29,7 +29,7 @@ pub fn bootstrap_validator_stake_lamports() -> u64 {
 
 // Number of lamports automatically used for genesis accounts
 pub const fn genesis_sysvar_and_builtin_program_lamports() -> u64 {
-    const NUM_BUILTIN_PROGRAMS: u64 = 7;
+    const NUM_BUILTIN_PROGRAMS: u64 = 6;
     const NUM_PRECOMPILES: u64 = 2;
     const STAKE_HISTORY_MIN_BALANCE: u64 = 114_979_200;
     const CLOCK_SYSVAR_MIN_BALANCE: u64 = 1_169_280;

+ 0 - 670
runtime/tests/stake.rs

@@ -1,670 +0,0 @@
-#![allow(clippy::arithmetic_side_effects)]
-
-use {
-    solana_account::{from_account, state_traits::StateMut},
-    solana_client_traits::SyncClient,
-    solana_clock::Slot,
-    solana_epoch_schedule::{EpochSchedule, MINIMUM_SLOTS_PER_EPOCH},
-    solana_keypair::Keypair,
-    solana_message::Message,
-    solana_pubkey::Pubkey,
-    solana_rent::Rent,
-    solana_runtime::{
-        bank::Bank,
-        bank_client::BankClient,
-        bank_forks::BankForks,
-        genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo},
-    },
-    solana_signer::Signer,
-    solana_stake_interface::{
-        self as stake, instruction as stake_instruction,
-        stake_history::StakeHistory,
-        state::{Authorized, Lockup, StakeStateV2},
-        sysvar,
-    },
-    solana_stake_program::stake_state,
-    solana_vote_program::{
-        vote_instruction,
-        vote_state::{TowerSync, VoteInit, VoteStateV3, VoteStateVersions, MAX_LOCKOUT_HISTORY},
-    },
-    std::sync::{Arc, RwLock},
-};
-
-fn new_bank_from_parent_with_bank_forks(
-    bank_forks: &RwLock<BankForks>,
-    parent: Arc<Bank>,
-    collector_id: &Pubkey,
-    slot: Slot,
-) -> Arc<Bank> {
-    let bank = Bank::new_from_parent(parent, collector_id, slot);
-    bank_forks
-        .write()
-        .unwrap()
-        .insert(bank)
-        .clone_without_scheduler()
-}
-
-/// get bank at next epoch + `n` slots
-fn next_epoch_and_n_slots(
-    bank: Arc<Bank>,
-    bank_forks: &RwLock<BankForks>,
-    mut n: usize,
-) -> Arc<Bank> {
-    bank.squash();
-    let slot = bank.get_slots_in_epoch(bank.epoch()) + bank.slot();
-    let mut bank = new_bank_from_parent_with_bank_forks(bank_forks, bank, &Pubkey::default(), slot);
-
-    while n > 0 {
-        bank.squash();
-        let slot = bank.slot() + 1;
-        bank = new_bank_from_parent_with_bank_forks(bank_forks, bank, &Pubkey::default(), slot);
-        n -= 1;
-    }
-
-    bank
-}
-
-fn fill_epoch_with_votes(
-    mut bank: Arc<Bank>,
-    bank_forks: &RwLock<BankForks>,
-    vote_keypair: &Keypair,
-    mint_keypair: &Keypair,
-    start_slot: Slot,
-) -> Arc<Bank> {
-    let mint_pubkey = mint_keypair.pubkey();
-    let vote_pubkey = vote_keypair.pubkey();
-    let old_epoch = bank.epoch();
-    while bank.epoch() != old_epoch + 1 {
-        bank.squash();
-        let slot = bank.slot() + 1;
-        bank = new_bank_from_parent_with_bank_forks(bank_forks, bank, &Pubkey::default(), slot);
-
-        let bank_client = BankClient::new_shared(bank.clone());
-        let parent = bank.parent().unwrap();
-        let lowest_slot = u64::max(
-            (parent.slot() + 1).saturating_sub(MAX_LOCKOUT_HISTORY as u64),
-            start_slot,
-        );
-        let slots: Vec<_> = (lowest_slot..(parent.slot() + 1)).collect();
-        let root = (lowest_slot > start_slot).then(|| lowest_slot - 1);
-        let tower_sync = TowerSync::new_from_slots(slots, parent.hash(), root);
-        let message = Message::new(
-            &[vote_instruction::tower_sync(
-                &vote_pubkey,
-                &vote_pubkey,
-                tower_sync,
-            )],
-            Some(&mint_pubkey),
-        );
-        assert!(bank_client
-            .send_and_confirm_message(&[mint_keypair, vote_keypair], message)
-            .is_ok());
-    }
-    bank
-}
-
-fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool {
-    let stake = stake_state::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap();
-
-    stake.delegation.stake
-        == stake.stake(
-            bank.epoch(),
-            &from_account::<StakeHistory, _>(
-                &bank.get_account(&sysvar::stake_history::id()).unwrap(),
-            )
-            .unwrap(),
-            bank.new_warmup_cooldown_rate_epoch(),
-        )
-}
-
-fn get_staked(bank: &Bank, stake_pubkey: &Pubkey) -> u64 {
-    stake_state::stake_from(&bank.get_account(stake_pubkey).unwrap())
-        .unwrap()
-        .stake(
-            bank.epoch(),
-            &from_account::<StakeHistory, _>(
-                &bank.get_account(&sysvar::stake_history::id()).unwrap(),
-            )
-            .unwrap(),
-            bank.new_warmup_cooldown_rate_epoch(),
-        )
-}
-
-#[test]
-fn test_stake_create_and_split_single_signature() {
-    solana_logger::setup();
-
-    let GenesisConfigInfo {
-        genesis_config,
-        mint_keypair: staker_keypair,
-        ..
-    } = create_genesis_config_with_leader(100_000_000_000, &solana_pubkey::new_rand(), 1_000_000);
-
-    let staker_pubkey = staker_keypair.pubkey();
-
-    let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
-    let bank_client = BankClient::new_shared(bank.clone());
-
-    let stake_address =
-        Pubkey::create_with_seed(&staker_pubkey, "stake", &stake::program::id()).unwrap();
-
-    let authorized = Authorized::auto(&staker_pubkey);
-
-    let lamports = {
-        let rent = &bank.rent_collector().rent;
-        let rent_exempt_reserve = rent.minimum_balance(StakeStateV2::size_of());
-        let minimum_delegation = solana_stake_program::get_minimum_delegation(
-            bank.feature_set
-                .is_active(&agave_feature_set::stake_raise_minimum_delegation_to_1_sol::id()),
-        );
-        2 * (rent_exempt_reserve + minimum_delegation)
-    };
-
-    // Create stake account with seed
-    let message = Message::new(
-        &stake_instruction::create_account_with_seed(
-            &staker_pubkey, // from
-            &stake_address, // to
-            &staker_pubkey, // base
-            "stake",        // seed
-            &authorized,
-            &Lockup::default(),
-            lamports,
-        ),
-        Some(&staker_pubkey),
-    );
-
-    // only one signature required
-    bank_client
-        .send_and_confirm_message(&[&staker_keypair], message)
-        .expect("failed to create and delegate stake account");
-
-    // split the stake
-    let split_stake_address =
-        Pubkey::create_with_seed(&staker_pubkey, "split_stake", &stake::program::id()).unwrap();
-    // Test split
-    let message = Message::new(
-        &stake_instruction::split_with_seed(
-            &stake_address, // original
-            &staker_pubkey, // authorized
-            lamports / 2,
-            &split_stake_address, // new address
-            &staker_pubkey,       // base
-            "split_stake",        // seed
-        ),
-        Some(&staker_keypair.pubkey()),
-    );
-
-    assert!(bank_client
-        .send_and_confirm_message(&[&staker_keypair], message)
-        .is_ok());
-
-    // w00t!
-}
-
-#[test]
-fn test_stake_create_and_split_to_existing_system_account() {
-    // Ensure stake-split allows the user to promote an existing system account into
-    // a stake account.
-
-    solana_logger::setup();
-
-    let GenesisConfigInfo {
-        genesis_config,
-        mint_keypair: staker_keypair,
-        ..
-    } = create_genesis_config_with_leader(100_000_000_000, &solana_pubkey::new_rand(), 1_000_000);
-
-    let staker_pubkey = staker_keypair.pubkey();
-
-    let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
-    let bank_client = BankClient::new_shared(bank.clone());
-
-    let stake_address =
-        Pubkey::create_with_seed(&staker_pubkey, "stake", &stake::program::id()).unwrap();
-
-    let authorized = Authorized::auto(&staker_pubkey);
-
-    let lamports = {
-        let rent = &bank.rent_collector().rent;
-        let rent_exempt_reserve = rent.minimum_balance(StakeStateV2::size_of());
-        let minimum_delegation = solana_stake_program::get_minimum_delegation(
-            bank.feature_set
-                .is_active(&agave_feature_set::stake_raise_minimum_delegation_to_1_sol::id()),
-        );
-        2 * (rent_exempt_reserve + minimum_delegation)
-    };
-
-    // Create stake account with seed
-    let message = Message::new(
-        &stake_instruction::create_account_with_seed(
-            &staker_pubkey, // from
-            &stake_address, // to
-            &staker_pubkey, // base
-            "stake",        // seed
-            &authorized,
-            &Lockup::default(),
-            lamports,
-        ),
-        Some(&staker_pubkey),
-    );
-
-    bank_client
-        .send_and_confirm_message(&[&staker_keypair], message)
-        .expect("failed to create and delegate stake account");
-
-    let split_stake_address =
-        Pubkey::create_with_seed(&staker_pubkey, "split_stake", &stake::program::id()).unwrap();
-
-    // First, put a system account where we want the new stake account
-    let existing_lamports = 42;
-    bank_client
-        .transfer_and_confirm(existing_lamports, &staker_keypair, &split_stake_address)
-        .unwrap();
-    assert_eq!(
-        bank_client.get_balance(&split_stake_address).unwrap(),
-        existing_lamports
-    );
-
-    // Verify the split succeeds with lamports in the destination account
-    let message = Message::new(
-        &stake_instruction::split_with_seed(
-            &stake_address, // original
-            &staker_pubkey, // authorized
-            lamports / 2,
-            &split_stake_address, // new address
-            &staker_pubkey,       // base
-            "split_stake",        // seed
-        ),
-        Some(&staker_keypair.pubkey()),
-    );
-    bank_client
-        .send_and_confirm_message(&[&staker_keypair], message)
-        .expect("failed to split into account with lamports");
-    assert_eq!(
-        bank_client.get_balance(&split_stake_address).unwrap(),
-        existing_lamports + lamports / 2
-    );
-}
-
-#[test]
-fn test_stake_account_lifetime() {
-    let stake_keypair = Keypair::new();
-    let stake_pubkey = stake_keypair.pubkey();
-    let vote_keypair = Keypair::new();
-    let vote_pubkey = vote_keypair.pubkey();
-    let identity_keypair = Keypair::new();
-    let identity_pubkey = identity_keypair.pubkey();
-
-    let GenesisConfigInfo {
-        mut genesis_config,
-        mint_keypair,
-        ..
-    } = create_genesis_config_with_leader(
-        100_000_000_000,
-        &solana_pubkey::new_rand(),
-        2_000_000_000,
-    );
-    genesis_config.epoch_schedule = EpochSchedule::new(MINIMUM_SLOTS_PER_EPOCH);
-    genesis_config.rent = Rent::default();
-    let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
-    let mint_pubkey = mint_keypair.pubkey();
-    let bank_client = BankClient::new_shared(bank.clone());
-
-    let (vote_balance, stake_rent_exempt_reserve, stake_minimum_delegation) = {
-        let rent = &bank.rent_collector().rent;
-        (
-            rent.minimum_balance(VoteStateV3::size_of()),
-            rent.minimum_balance(StakeStateV2::size_of()),
-            solana_stake_program::get_minimum_delegation(
-                bank.feature_set
-                    .is_active(&agave_feature_set::stake_raise_minimum_delegation_to_1_sol::id()),
-            ),
-        )
-    };
-
-    // Create Vote Account
-    let message = Message::new(
-        &vote_instruction::create_account_with_config(
-            &mint_pubkey,
-            &vote_pubkey,
-            &VoteInit {
-                node_pubkey: identity_pubkey,
-                authorized_voter: vote_pubkey,
-                authorized_withdrawer: vote_pubkey,
-                commission: 50,
-            },
-            vote_balance,
-            vote_instruction::CreateVoteAccountConfig {
-                space: VoteStateVersions::vote_state_size_of(true) as u64,
-                ..vote_instruction::CreateVoteAccountConfig::default()
-            },
-        ),
-        Some(&mint_pubkey),
-    );
-    bank_client
-        .send_and_confirm_message(&[&mint_keypair, &vote_keypair, &identity_keypair], message)
-        .expect("failed to create vote account");
-
-    let authorized = Authorized::auto(&stake_pubkey);
-    let bonus_delegation = 1_000_000_000;
-    let stake_starting_delegation =
-        2 * stake_minimum_delegation + bonus_delegation + stake_rent_exempt_reserve;
-    let stake_starting_balance = stake_starting_delegation + stake_rent_exempt_reserve;
-
-    // Create stake account and delegate to vote account
-    let message = Message::new(
-        &stake_instruction::create_account_and_delegate_stake(
-            &mint_pubkey,
-            &stake_pubkey,
-            &vote_pubkey,
-            &authorized,
-            &Lockup::default(),
-            stake_starting_balance,
-        ),
-        Some(&mint_pubkey),
-    );
-    bank_client
-        .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message)
-        .expect("failed to create and delegate stake account");
-
-    // Test that correct lamports are staked
-    let account = bank.get_account(&stake_pubkey).expect("account not found");
-    let stake_state = account.state().expect("couldn't unpack account data");
-    if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state {
-        assert_eq!(stake.delegation.stake, stake_starting_delegation,);
-    } else {
-        panic!("wrong account type found")
-    }
-
-    // Test that we cannot withdraw anything until deactivation
-    let message = Message::new(
-        &[stake_instruction::withdraw(
-            &stake_pubkey,
-            &stake_pubkey,
-            &solana_pubkey::new_rand(),
-            1,
-            None,
-        )],
-        Some(&mint_pubkey),
-    );
-    assert!(bank_client
-        .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message)
-        .is_err());
-
-    // Test that lamports are still staked
-    let account = bank.get_account(&stake_pubkey).expect("account not found");
-    let stake_state = account.state().expect("couldn't unpack account data");
-    if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state {
-        assert_eq!(stake.delegation.stake, stake_starting_delegation,);
-    } else {
-        panic!("wrong account type found")
-    }
-
-    loop {
-        if warmed_up(&bank, &stake_pubkey) {
-            break;
-        }
-        // Cycle thru banks until we're fully warmed up
-        bank = next_epoch_and_n_slots(bank, bank_forks.as_ref(), 0);
-    }
-
-    // Reward redemption
-    // Submit enough votes to generate rewards
-    let start_slot = bank.slot();
-    bank = fill_epoch_with_votes(
-        bank,
-        bank_forks.as_ref(),
-        &vote_keypair,
-        &mint_keypair,
-        start_slot,
-    );
-
-    // Test that votes and credits are there
-    let account = bank.get_account(&vote_pubkey).expect("account not found");
-    let vote_state: VoteStateV3 = StateMut::<VoteStateVersions>::state(&account)
-        .expect("couldn't unpack account data")
-        .convert_to_v3();
-
-    // 1 less vote, as the first vote should have cleared the lockout
-    assert_eq!(vote_state.votes.len(), 31);
-    // one vote per slot, might be more slots than 32 in the epoch
-    assert!(vote_state.credits() >= 1);
-
-    bank = fill_epoch_with_votes(
-        bank,
-        bank_forks.as_ref(),
-        &vote_keypair,
-        &mint_keypair,
-        start_slot,
-    );
-
-    let pre_staked = get_staked(&bank, &stake_pubkey);
-    let pre_balance = bank.get_balance(&stake_pubkey);
-
-    // next epoch bank plus one additional slot should pay rewards
-    bank = next_epoch_and_n_slots(bank, bank_forks.as_ref(), 1);
-
-    // Test that balance increased, and that the balance got staked
-    let staked = get_staked(&bank, &stake_pubkey);
-    let balance = bank.get_balance(&stake_pubkey);
-    assert!(staked > pre_staked);
-    assert!(balance > pre_balance);
-
-    // split the stake
-    let split_stake_keypair = Keypair::new();
-    let split_stake_pubkey = split_stake_keypair.pubkey();
-
-    bank.transfer(
-        stake_rent_exempt_reserve,
-        &mint_keypair,
-        &split_stake_pubkey,
-    )
-    .unwrap();
-    let bank_client = BankClient::new_shared(bank.clone());
-
-    // Test split
-    let split_starting_delegation = stake_minimum_delegation + bonus_delegation;
-    let message = Message::new(
-        &stake_instruction::split(
-            &stake_pubkey,
-            &stake_pubkey,
-            split_starting_delegation,
-            &split_stake_pubkey,
-        ),
-        Some(&mint_pubkey),
-    );
-    assert!(bank_client
-        .send_and_confirm_message(
-            &[&mint_keypair, &stake_keypair, &split_stake_keypair],
-            message
-        )
-        .is_ok());
-    assert_eq!(
-        get_staked(&bank, &split_stake_pubkey),
-        split_starting_delegation,
-    );
-    let stake_remaining_balance = balance - split_starting_delegation;
-
-    // Deactivate the split
-    let message = Message::new(
-        &[stake_instruction::deactivate_stake(
-            &split_stake_pubkey,
-            &stake_pubkey,
-        )],
-        Some(&mint_pubkey),
-    );
-    assert!(bank_client
-        .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message)
-        .is_ok());
-    assert_eq!(
-        get_staked(&bank, &split_stake_pubkey),
-        split_starting_delegation,
-    );
-
-    // Test that we cannot withdraw above what's staked
-    let message = Message::new(
-        &[stake_instruction::withdraw(
-            &split_stake_pubkey,
-            &stake_pubkey,
-            &solana_pubkey::new_rand(),
-            split_starting_delegation + 1,
-            None,
-        )],
-        Some(&mint_pubkey),
-    );
-    assert!(bank_client
-        .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message)
-        .is_err());
-
-    let mut bank = next_epoch_and_n_slots(bank, bank_forks.as_ref(), 1);
-
-    let bank_client = BankClient::new_shared(bank.clone());
-
-    // assert we're still cooling down
-    let split_staked = get_staked(&bank, &split_stake_pubkey);
-    assert!(split_staked > 0);
-
-    // withdrawal in cooldown
-    let split_balance = bank.get_balance(&split_stake_pubkey);
-    let message = Message::new(
-        &[stake_instruction::withdraw(
-            &split_stake_pubkey,
-            &stake_pubkey,
-            &solana_pubkey::new_rand(),
-            split_balance,
-            None,
-        )],
-        Some(&mint_pubkey),
-    );
-    assert!(bank_client
-        .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message)
-        .is_err());
-
-    // but we can withdraw unstaked
-    let split_unstaked = split_balance - split_staked - stake_rent_exempt_reserve;
-    assert!(split_unstaked > 0);
-    let message = Message::new(
-        &[stake_instruction::withdraw(
-            &split_stake_pubkey,
-            &stake_pubkey,
-            &solana_pubkey::new_rand(),
-            split_unstaked,
-            None,
-        )],
-        Some(&mint_pubkey),
-    );
-    assert!(bank_client
-        .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message)
-        .is_ok());
-
-    // finish cooldown
-    loop {
-        if get_staked(&bank, &split_stake_pubkey) == 0 {
-            break;
-        }
-        bank = next_epoch_and_n_slots(bank, bank_forks.as_ref(), 1);
-    }
-    let bank_client = BankClient::new_shared(bank.clone());
-
-    // Test that we can withdraw everything else out of the split
-    let split_remaining_balance = split_balance - split_unstaked;
-    let message = Message::new(
-        &[stake_instruction::withdraw(
-            &split_stake_pubkey,
-            &stake_pubkey,
-            &solana_pubkey::new_rand(),
-            split_remaining_balance,
-            None,
-        )],
-        Some(&mint_pubkey),
-    );
-    assert!(bank_client
-        .send_and_confirm_message(&[&mint_keypair, &stake_keypair], message)
-        .is_ok());
-
-    // verify all the math sums to zero
-    assert_eq!(bank.get_balance(&split_stake_pubkey), 0);
-    assert_eq!(bank.get_balance(&stake_pubkey), stake_remaining_balance);
-}
-
-#[test]
-fn test_create_stake_account_from_seed() {
-    let vote_keypair = Keypair::new();
-    let vote_pubkey = vote_keypair.pubkey();
-    let identity_keypair = Keypair::new();
-    let identity_pubkey = identity_keypair.pubkey();
-
-    let GenesisConfigInfo {
-        genesis_config,
-        mint_keypair,
-        ..
-    } = create_genesis_config_with_leader(100_000_000_000, &solana_pubkey::new_rand(), 1_000_000);
-    let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
-    let mint_pubkey = mint_keypair.pubkey();
-    let bank_client = BankClient::new_shared(bank.clone());
-
-    let seed = "test-string";
-    let stake_pubkey = Pubkey::create_with_seed(&mint_pubkey, seed, &stake::program::id()).unwrap();
-
-    // Create Vote Account
-    let message = Message::new(
-        &vote_instruction::create_account_with_config(
-            &mint_pubkey,
-            &vote_pubkey,
-            &VoteInit {
-                node_pubkey: identity_pubkey,
-                authorized_voter: vote_pubkey,
-                authorized_withdrawer: vote_pubkey,
-                commission: 50,
-            },
-            10,
-            vote_instruction::CreateVoteAccountConfig {
-                space: VoteStateVersions::vote_state_size_of(true) as u64,
-                ..vote_instruction::CreateVoteAccountConfig::default()
-            },
-        ),
-        Some(&mint_pubkey),
-    );
-    bank_client
-        .send_and_confirm_message(&[&mint_keypair, &vote_keypair, &identity_keypair], message)
-        .expect("failed to create vote account");
-
-    let authorized = Authorized::auto(&mint_pubkey);
-    let (balance, delegation) = {
-        let rent = &bank.rent_collector().rent;
-        let rent_exempt_reserve = rent.minimum_balance(StakeStateV2::size_of());
-        let minimum_delegation = solana_stake_program::get_minimum_delegation(
-            bank.feature_set
-                .is_active(&agave_feature_set::stake_raise_minimum_delegation_to_1_sol::id()),
-        );
-        (rent_exempt_reserve + minimum_delegation, minimum_delegation)
-    };
-
-    // Create stake account and delegate to vote account
-    let message = Message::new(
-        &stake_instruction::create_account_with_seed_and_delegate_stake(
-            &mint_pubkey,
-            &stake_pubkey,
-            &mint_pubkey,
-            seed,
-            &vote_pubkey,
-            &authorized,
-            &Lockup::default(),
-            balance,
-        ),
-        Some(&mint_pubkey),
-    );
-    bank_client
-        .send_and_confirm_message(&[&mint_keypair], message)
-        .expect("failed to create and delegate stake account");
-
-    // Test that correct lamports are staked
-    let account = bank.get_account(&stake_pubkey).expect("account not found");
-    let stake_state = account.state().expect("couldn't unpack account data");
-    if let StakeStateV2::Stake(_meta, stake, _) = stake_state {
-        assert_eq!(stake.delegation.stake, delegation);
-    } else {
-        panic!("wrong account type found")
-    }
-}

+ 2 - 0
stake-accounts/Cargo.toml

@@ -34,9 +34,11 @@ solana-signature = { workspace = true }
 solana-signer = { workspace = true }
 solana-stake-interface = { workspace = true }
 solana-stake-program = { workspace = true }
+solana-sysvar = { workspace = true }
 solana-transaction = { workspace = true }
 solana-version = { workspace = true }
 
 [dev-dependencies]
 solana-client-traits = { workspace = true }
+solana-program-binaries = { workspace = true }
 solana-runtime = { workspace = true, features = ["dev-context-only-utils"] }

+ 21 - 1
stake-accounts/src/stake_accounts.rs

@@ -290,16 +290,36 @@ mod tests {
         solana_signer::Signer,
         solana_stake_interface::state::StakeStateV2,
         solana_stake_program::stake_state,
+        solana_sysvar::epoch_rewards::EpochRewards,
         std::sync::{Arc, RwLock},
     };
 
     fn create_bank(lamports: u64) -> (Arc<Bank>, Arc<RwLock<BankForks>>, Keypair, u64, u64) {
         let (mut genesis_config, mint_keypair) = create_genesis_config(lamports);
         genesis_config.fee_rate_governor = solana_fee_calculator::FeeRateGovernor::new(0, 0);
+
+        for (pubkey, account) in
+            solana_program_binaries::by_id(&stake::program::id(), &genesis_config.rent)
+                .unwrap()
+                .into_iter()
+        {
+            genesis_config.add_account(pubkey, account);
+        }
+
         let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
+        bank.squash();
+        let bank = Bank::new_from_parent(bank, &Pubkey::new_unique(), 1);
+        bank.set_sysvar_for_tests(&EpochRewards::default());
+
         let stake_rent = bank.get_minimum_balance_for_rent_exemption(StakeStateV2::size_of());
         let system_rent = bank.get_minimum_balance_for_rent_exemption(0);
-        (bank, bank_forks, mint_keypair, stake_rent, system_rent)
+        (
+            bank.into(),
+            bank_forks,
+            mint_keypair,
+            stake_rent,
+            system_rent,
+        )
     }
 
     fn create_account<C: SyncClient>(

+ 1 - 0
svm/Cargo.toml

@@ -100,6 +100,7 @@ solana-keypair = { workspace = true }
 solana-logger = { workspace = true }
 solana-native-token = { workspace = true }
 solana-precompile-error = { workspace = true }
+solana-program-binaries = { workspace = true }
 solana-program-runtime = { workspace = true, features = ["dev-context-only-utils"] }
 solana-pubkey = { workspace = true, features = ["rand"] }
 solana-rent = { workspace = true }

+ 4 - 12
svm/tests/integration_test.rs

@@ -3285,7 +3285,6 @@ mod balance_collector {
         super::*,
         rand0_7::prelude::*,
         solana_program_pack::Pack,
-        solana_sdk_ids::bpf_loader,
         spl_generic_token::token_2022,
         spl_token_interface::state::{
             Account as TokenAccount, AccountState as TokenAccountState, Mint,
@@ -3293,10 +3292,6 @@ mod balance_collector {
         test_case::test_case,
     };
 
-    // this could be part of mock_bank but so far nothing but this uses it
-    static SPL_TOKEN_BYTES: &[u8] =
-        include_bytes!("../../program-test/src/programs/spl_token-3.5.0.so");
-
     const STARTING_BALANCE: u64 = LAMPORTS_PER_SOL * 100;
 
     // a helper for constructing a transfer instruction, agnostic over system/token
@@ -3417,13 +3412,10 @@ mod balance_collector {
             u64::MAX,
         );
 
-        let spl_token = AccountSharedData::create(
-            LAMPORTS_PER_SOL,
-            SPL_TOKEN_BYTES.to_vec(),
-            bpf_loader::id(),
-            true,
-            u64::MAX,
-        );
+        let (_, spl_token) =
+            solana_program_binaries::by_id(&spl_token_interface::id(), &Rent::default())
+                .unwrap()
+                .swap_remove(0);
 
         for _ in 0..100 {
             let mut test_entry = SvmTestEntry::default();

+ 1 - 0
test-validator/Cargo.toml

@@ -42,6 +42,7 @@ solana-logger = "=3.0.0"
 solana-message = { workspace = true }
 solana-native-token = { workspace = true }
 solana-net-utils = { workspace = true }
+solana-program-binaries = { workspace = true }
 solana-program-test = { workspace = true }
 solana-pubkey = { workspace = true }
 solana-rent = { workspace = true }

+ 5 - 11
test-validator/src/lib.rs

@@ -884,11 +884,11 @@ impl TestValidator {
         }
 
         let mut accounts = config.accounts.clone();
-        for (address, account) in solana_program_test::programs::spl_programs(&config.rent) {
+        for (address, account) in solana_program_binaries::spl_programs(&config.rent) {
             accounts.entry(address).or_insert(account);
         }
         for (address, account) in
-            solana_program_test::programs::core_bpf_programs(&config.rent, |feature_id| {
+            solana_program_binaries::core_bpf_programs(&config.rent, |feature_id| {
                 feature_set.contains(feature_id)
             })
         {
@@ -1516,13 +1516,7 @@ mod test {
 
     #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
     async fn test_core_bpf_programs() {
-        let (test_validator, _payer) = TestValidatorGenesis::default()
-            .deactivate_features(&[
-                // Don't migrate the stake program.
-                agave_feature_set::migrate_stake_program_to_core_bpf::id(),
-            ])
-            .start_async()
-            .await;
+        let (test_validator, _payer) = TestValidatorGenesis::default().start_async().await;
 
         let rpc_client = test_validator.get_async_rpc_client();
 
@@ -1551,9 +1545,9 @@ mod test {
         assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
         assert!(account.executable);
 
-        // Stake is a builtin.
+        // Stake is a BPF program.
         let account = fetched_programs[3].as_ref().unwrap();
-        assert_eq!(account.owner, solana_sdk_ids::native_loader::id());
+        assert_eq!(account.owner, solana_sdk_ids::bpf_loader_upgradeable::id());
         assert!(account.executable);
     }
 }