diff --git a/Cargo.lock b/Cargo.lock index d495432070..ae6af006ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4125,7 +4125,6 @@ dependencies = [ "serde_json", "solana-config-program", "solana-sdk", - "solana-stake-program", "solana-vote-program", "spl-token", "thiserror", @@ -4405,7 +4404,6 @@ dependencies = [ "solana-net-utils", "solana-remote-wallet", "solana-sdk", - "solana-stake-program", "solana-transaction-status", "solana-version", "solana-vote-program", @@ -4446,7 +4444,6 @@ dependencies = [ "solana-clap-utils", "solana-client", "solana-sdk", - "solana-stake-program", "solana-transaction-status", "solana-vote-program", "spl-memo", @@ -4907,7 +4904,6 @@ dependencies = [ "solana-rayon-threadlimit", "solana-runtime", "solana-sdk", - "solana-stake-program", "solana-storage-bigtable", "solana-storage-proto", "solana-transaction-status", @@ -5144,7 +5140,6 @@ dependencies = [ "solana-metrics", "solana-rayon-threadlimit", "solana-sdk", - "solana-stake-program", ] [[package]] @@ -5274,7 +5269,6 @@ dependencies = [ "solana-logger 1.8.0", "solana-runtime", "solana-sdk", - "solana-stake-program", "solana-vote-program", "thiserror", "tokio 1.6.1", @@ -5532,7 +5526,6 @@ dependencies = [ "solana-metrics", "solana-rpc", "solana-sdk", - "solana-stake-program", "solana-transaction-status", "solana-version", "tempfile", @@ -5669,7 +5662,6 @@ dependencies = [ "solana-remote-wallet", "solana-runtime", "solana-sdk", - "solana-stake-program", "solana-transaction-status", "solana-version", "spl-associated-token-account", @@ -5693,7 +5685,6 @@ dependencies = [ "solana-account-decoder", "solana-runtime", "solana-sdk", - "solana-stake-program", "solana-vote-program", "spl-associated-token-account", "spl-memo", diff --git a/account-decoder/Cargo.toml b/account-decoder/Cargo.toml index 90b6622dc6..c572375f27 100644 --- a/account-decoder/Cargo.toml +++ b/account-decoder/Cargo.toml @@ -21,7 +21,6 @@ serde_derive = "1.0.103" serde_json = "1.0.64" solana-config-program = { path = "../programs/config", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-vote-program = { path = "../programs/vote", version = "=1.8.0" } spl-token-v2-0 = { package = "spl-token", version = "=3.1.1", features = ["no-entrypoint"] } thiserror = "1.0" diff --git a/account-decoder/src/parse_account_data.rs b/account-decoder/src/parse_account_data.rs index 9b649dc8fe..0c26b4003e 100644 --- a/account-decoder/src/parse_account_data.rs +++ b/account-decoder/src/parse_account_data.rs @@ -9,14 +9,14 @@ use crate::{ }; use inflector::Inflector; use serde_json::Value; -use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar}; +use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, stake, system_program, sysvar}; use std::collections::HashMap; use thiserror::Error; lazy_static! { static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id(); static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id(); - static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id(); + static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id(); static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id(); static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id(); static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0(); diff --git a/account-decoder/src/parse_config.rs b/account-decoder/src/parse_config.rs index 3a9d6151b5..c545c8c0d8 100644 --- a/account-decoder/src/parse_config.rs +++ b/account-decoder/src/parse_config.rs @@ -6,10 +6,10 @@ use bincode::deserialize; use serde_json::Value; use solana_config_program::{get_config_data, ConfigKeys}; use solana_sdk::pubkey::Pubkey; -use solana_stake_program::config::Config as StakeConfig; +use solana_sdk::stake::config::{self as stake_config, Config as StakeConfig}; pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result { - let parsed_account = if pubkey == &solana_stake_program::config::id() { + let parsed_account = if pubkey == &stake_config::id() { get_config_data(data) .ok() .and_then(|data| deserialize::(data).ok()) @@ -101,11 +101,7 @@ mod test { }; let stake_config_account = create_config_account(vec![], &stake_config, 10); assert_eq!( - parse_config( - &stake_config_account.data(), - &solana_stake_program::config::id() - ) - .unwrap(), + parse_config(&stake_config_account.data(), &stake_config::id()).unwrap(), ConfigAccountType::StakeConfig(UiStakeConfig { warmup_cooldown_rate: 0.25, slash_penalty: 50, diff --git a/account-decoder/src/parse_stake.rs b/account-decoder/src/parse_stake.rs index 86dc88e80a..fa3d3def6a 100644 --- a/account-decoder/src/parse_stake.rs +++ b/account-decoder/src/parse_stake.rs @@ -4,7 +4,7 @@ use crate::{ }; use bincode::deserialize; use solana_sdk::clock::{Epoch, UnixTimestamp}; -use solana_stake_program::stake_state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState}; +use solana_sdk::stake::state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState}; pub fn parse_stake(data: &[u8]) -> Result { let stake_state: StakeState = deserialize(data) diff --git a/cli-output/Cargo.toml b/cli-output/Cargo.toml index 1f25d3bb23..2a3dffa7e4 100644 --- a/cli-output/Cargo.toml +++ b/cli-output/Cargo.toml @@ -23,7 +23,6 @@ solana-account-decoder = { path = "../account-decoder", version = "=1.8.0" } solana-clap-utils = { path = "../clap-utils", version = "=1.8.0" } solana-client = { path = "../client", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-transaction-status = { path = "../transaction-status", version = "=1.8.0" } solana-vote-program = { path = "../programs/vote", version = "=1.8.0" } spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] } diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index 7532a41d93..a1605b587a 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -25,10 +25,10 @@ use { native_token::lamports_to_sol, pubkey::Pubkey, signature::Signature, + stake::state::{Authorized, Lockup}, stake_history::StakeHistoryEntry, transaction::{Transaction, TransactionError}, }, - solana_stake_program::stake_state::{Authorized, Lockup}, solana_transaction_status::{ EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus, UiTransactionStatusMeta, diff --git a/cli-output/src/display.rs b/cli-output/src/display.rs index ec4f8b4a41..4961feb0f6 100644 --- a/cli-output/src/display.rs +++ b/cli-output/src/display.rs @@ -5,7 +5,7 @@ use { indicatif::{ProgressBar, ProgressStyle}, solana_sdk::{ clock::UnixTimestamp, hash::Hash, message::Message, native_token::lamports_to_sol, - program_utils::limited_deserialize, pubkey::Pubkey, transaction::Transaction, + program_utils::limited_deserialize, pubkey::Pubkey, stake, transaction::Transaction, }, solana_transaction_status::UiTransactionStatusMeta, spl_memo::id as spl_memo_id, @@ -244,10 +244,9 @@ pub fn write_transaction( writeln!(w, "{} {:?}", prefix, vote_instruction)?; raw = false; } - } else if program_pubkey == solana_stake_program::id() { - if let Ok(stake_instruction) = limited_deserialize::< - solana_stake_program::stake_instruction::StakeInstruction, - >(&instruction.data) + } else if program_pubkey == stake::program::id() { + if let Ok(stake_instruction) = + limited_deserialize::(&instruction.data) { writeln!(w, "{} {:?}", prefix, stake_instruction)?; raw = false; diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d7006e7f25..919a642910 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -41,7 +41,6 @@ solana-net-utils = { path = "../net-utils", version = "=1.8.0" } solana_rbpf = "=0.2.12" solana-remote-wallet = { path = "../remote-wallet", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-transaction-status = { path = "../transaction-status", version = "=1.8.0" } solana-version = { path = "../version", version = "=1.8.0" } solana-vote-program = { path = "../programs/vote", version = "=1.8.0" } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 78aaf760dc..a306d8c63e 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -45,14 +45,15 @@ use solana_sdk::{ message::Message, pubkey::Pubkey, signature::{Signature, Signer, SignerError}, + stake::{ + self, + instruction::LockupArgs, + state::{Lockup, StakeAuthorize}, + }, system_instruction::{self, SystemError}, system_program, transaction::{Transaction, TransactionError}, }; -use solana_stake_program::{ - stake_instruction::LockupArgs, - stake_state::{Lockup, StakeAuthorize}, -}; use solana_transaction_status::{EncodedTransaction, UiTransactionEncoding}; use solana_vote_program::vote_state::VoteAuthorize; use std::{ @@ -932,7 +933,7 @@ pub type ProcessResult = Result>; fn resolve_derived_address_program_id(matches: &ArgMatches<'_>, arg_name: &str) -> Option { matches.value_of(arg_name).and_then(|v| match v { "NONCE" => Some(system_program::id()), - "STAKE" => Some(solana_stake_program::id()), + "STAKE" => Some(stake::program::id()), "VOTE" => Some(solana_vote_program::id()), _ => pubkey_of(matches, arg_name), }) @@ -2487,7 +2488,7 @@ mod tests { let from_pubkey = Some(solana_sdk::pubkey::new_rand()); let from_str = from_pubkey.unwrap().to_string(); for (name, program_id) in &[ - ("STAKE", solana_stake_program::id()), + ("STAKE", stake::program::id()), ("VOTE", solana_vote_program::id()), ("NONCE", system_program::id()), ] { @@ -2523,7 +2524,7 @@ mod tests { command: CliCommand::CreateAddressWithSeed { from_pubkey: None, seed: "seed".to_string(), - program_id: solana_stake_program::id(), + program_id: stake::program::id(), }, signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -2786,11 +2787,11 @@ mod tests { config.command = CliCommand::CreateAddressWithSeed { from_pubkey: Some(from_pubkey), seed: "seed".to_string(), - program_id: solana_stake_program::id(), + program_id: stake::program::id(), }; let address = process_command(&config); let expected_address = - Pubkey::create_with_seed(&from_pubkey, "seed", &solana_stake_program::id()).unwrap(); + Pubkey::create_with_seed(&from_pubkey, "seed", &stake::program::id()).unwrap(); assert_eq!(address.unwrap(), expected_address.to_string()); // Need airdrop cases @@ -3177,7 +3178,7 @@ mod tests { memo: None, fee_payer: 0, derived_address_seed: Some(derived_address_seed), - derived_address_program_id: Some(solana_stake_program::id()), + derived_address_program_id: Some(stake::program::id()), }, signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),], } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index ff5d0cc0f7..236ce9c2b3 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -46,7 +46,9 @@ use solana_sdk::{ rent::Rent, rpc_port::DEFAULT_RPC_PORT_STR, signature::Signature, - slot_history, system_instruction, system_program, + slot_history, + stake::{self, state::StakeState}, + system_instruction, system_program, sysvar::{ self, slot_history::SlotHistory, @@ -55,7 +57,6 @@ use solana_sdk::{ timing, transaction::Transaction, }; -use solana_stake_program::stake_state::StakeState; use solana_transaction_status::UiTransactionEncoding; use solana_vote_program::vote_state::VoteState; use std::{ @@ -1704,7 +1705,7 @@ pub fn process_show_stakes( } } let all_stake_accounts = rpc_client - .get_program_accounts_with_config(&solana_stake_program::id(), program_accounts_config)?; + .get_program_accounts_with_config(&stake::program::id(), program_accounts_config)?; let stake_history_account = rpc_client.get_account(&stake_history::id())?; let clock_account = rpc_client.get_account(&sysvar::clock::id())?; let clock: Clock = from_account(&clock_account).ok_or_else(|| { diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 1bdb3c302f..42d21fea41 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -36,6 +36,11 @@ use solana_sdk::{ feature, feature_set, message::Message, pubkey::Pubkey, + stake::{ + self, + instruction::{self as stake_instruction, LockupArgs, StakeError}, + state::{Authorized, Lockup, Meta, StakeAuthorize, StakeState}, + }, system_instruction::SystemError, sysvar::{ clock, @@ -43,10 +48,6 @@ use solana_sdk::{ }, transaction::Transaction, }; -use solana_stake_program::{ - stake_instruction::{self, LockupArgs, StakeError}, - stake_state::{Authorized, Lockup, Meta, StakeAuthorize, StakeState}, -}; use solana_vote_program::vote_state::VoteState; use std::{ops::Deref, sync::Arc}; @@ -971,7 +972,7 @@ pub fn process_create_stake_account( ) -> ProcessResult { let stake_account = config.signers[stake_account]; let stake_account_address = if let Some(seed) = seed { - Pubkey::create_with_seed(&stake_account.pubkey(), &seed, &solana_stake_program::id())? + Pubkey::create_with_seed(&stake_account.pubkey(), &seed, &stake::program::id())? } else { stake_account.pubkey() }; @@ -1039,7 +1040,7 @@ pub fn process_create_stake_account( if !sign_only { if let Ok(stake_account) = rpc_client.get_account(&stake_account_address) { - let err_msg = if stake_account.owner == solana_stake_program::id() { + let err_msg = if stake_account.owner == stake::program::id() { format!("Stake account {} already exists", stake_account_address) } else { format!( @@ -1195,7 +1196,7 @@ pub fn process_deactivate_stake_account( let stake_authority = config.signers[stake_authority]; let stake_account_address = if let Some(seed) = seed { - Pubkey::create_with_seed(&stake_account_pubkey, seed, &solana_stake_program::id())? + Pubkey::create_with_seed(&stake_account_pubkey, seed, &stake::program::id())? } else { *stake_account_pubkey }; @@ -1273,7 +1274,7 @@ pub fn process_withdraw_stake( let custodian = custodian.map(|index| config.signers[index]); let stake_account_address = if let Some(seed) = seed { - Pubkey::create_with_seed(&stake_account_pubkey, seed, &solana_stake_program::id())? + Pubkey::create_with_seed(&stake_account_pubkey, seed, &stake::program::id())? } else { *stake_account_pubkey }; @@ -1394,18 +1395,14 @@ pub fn process_split_stake( let stake_authority = config.signers[stake_authority]; let split_stake_account_address = if let Some(seed) = split_stake_account_seed { - Pubkey::create_with_seed( - &split_stake_account.pubkey(), - &seed, - &solana_stake_program::id(), - )? + Pubkey::create_with_seed(&split_stake_account.pubkey(), &seed, &stake::program::id())? } else { split_stake_account.pubkey() }; if !sign_only { if let Ok(stake_account) = rpc_client.get_account(&split_stake_account_address) { - let err_msg = if stake_account.owner == solana_stake_program::id() { + let err_msg = if stake_account.owner == stake::program::id() { format!( "Stake account {} already exists", split_stake_account_address @@ -1540,7 +1537,7 @@ pub fn process_merge_stake( if !sign_only { for stake_account_address in &[stake_account_pubkey, source_stake_account_pubkey] { if let Ok(stake_account) = rpc_client.get_account(stake_account_address) { - if stake_account.owner != solana_stake_program::id() { + if stake_account.owner != stake::program::id() { return Err(CliError::BadParameter(format!( "Account {} is not a stake account", stake_account_address @@ -1876,7 +1873,7 @@ pub fn process_show_stake_account( with_rewards: Option, ) -> ProcessResult { let stake_account = rpc_client.get_account(stake_account_address)?; - if stake_account.owner != solana_stake_program::id() { + if stake_account.owner != stake::program::id() { return Err(CliError::RpcRequestError(format!( "{:?} is not a stake account", stake_account_address, diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index 51c3bd9770..162aa45134 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -17,10 +17,11 @@ use solana_sdk::{ nonce::State as NonceState, pubkey::Pubkey, signature::{keypair_from_seed, Keypair, Signer}, -}; -use solana_stake_program::{ - stake_instruction::LockupArgs, - stake_state::{Lockup, StakeAuthorize, StakeState}, + stake::{ + self, + instruction::LockupArgs, + state::{Lockup, StakeAuthorize, StakeState}, + }, }; #[test] @@ -139,7 +140,7 @@ fn test_seed_stake_delegation_and_deactivation() { let stake_address = Pubkey::create_with_seed( &config_validator.signers[0].pubkey(), "hi there", - &solana_stake_program::id(), + &stake::program::id(), ) .expect("bad seed"); @@ -1557,6 +1558,6 @@ fn test_offline_nonced_create_stake_account_and_withdraw() { }; process_command(&config).unwrap(); let seed_address = - Pubkey::create_with_seed(&stake_pubkey, seed, &solana_stake_program::id()).unwrap(); + Pubkey::create_with_seed(&stake_pubkey, seed, &stake::program::id()).unwrap(); check_recent_balance(50_000, &rpc_client, &seed_address); } diff --git a/cli/tests/transfer.rs b/cli/tests/transfer.rs index 3d97158830..86897c1894 100644 --- a/cli/tests/transfer.rs +++ b/cli/tests/transfer.rs @@ -16,6 +16,7 @@ use solana_sdk::{ nonce::State as NonceState, pubkey::Pubkey, signature::{keypair_from_seed, Keypair, NullSigner, Signer}, + stake, }; #[test] @@ -513,7 +514,7 @@ fn test_transfer_with_seed() { let sender_pubkey = config.signers[0].pubkey(); let recipient_pubkey = Pubkey::new(&[1u8; 32]); let derived_address_seed = "seed".to_string(); - let derived_address_program_id = solana_stake_program::id(); + let derived_address_program_id = stake::program::id(); let derived_address = Pubkey::create_with_seed( &sender_pubkey, &derived_address_seed, diff --git a/core/Cargo.toml b/core/Cargo.toml index 222238dba9..502b7415c0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -63,7 +63,6 @@ solana-runtime = { path = "../runtime", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } solana-frozen-abi = { path = "../frozen-abi", version = "=1.8.0" } solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-streamer = { path = "../streamer", version = "=1.8.0" } solana-transaction-status = { path = "../transaction-status", version = "=1.8.0" } solana-vote-program = { path = "../programs/vote", version = "=1.8.0" } @@ -80,6 +79,7 @@ num_cpus = "1.13.0" reqwest = { version = "0.11.2", default-features = false, features = ["blocking", "rustls-tls", "json"] } serde_json = "1.0.56" serial_test = "0.5.1" +solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-version = { path = "../version", version = "=1.8.0" } symlink = "0.1.0" systemstat = "0.1.8" diff --git a/docs/src/developing/runtime-facilities/programs.md b/docs/src/developing/runtime-facilities/programs.md index 74cda44ee0..c66afe9841 100644 --- a/docs/src/developing/runtime-facilities/programs.md +++ b/docs/src/developing/runtime-facilities/programs.md @@ -41,7 +41,7 @@ Create and manage accounts representing stake and rewards for delegations to validators. - Program id: `Stake11111111111111111111111111111111111111` -- Instructions: [StakeInstruction](https://docs.rs/solana-stake-program/VERSION_FOR_DOCS_RS/solana_stake_program/stake_instruction/enum.StakeInstruction.html) +- Instructions: [StakeInstruction](https://docs.rs/solana-sdk/VERSION_FOR_DOCS_RS/solana_sdk/stake/instruction/enum.StakeInstruction.html) ## Vote Program diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 63a83930e9..cb4ed366c7 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -26,9 +26,10 @@ use solana_sdk::{ pubkey::Pubkey, rent::Rent, signature::{Keypair, Signer}, + stake::state::StakeState, system_program, timing, }; -use solana_stake_program::stake_state::{self, StakeState}; +use solana_stake_program::stake_state; use solana_vote_program::vote_state::{self, VoteState}; use std::{ collections::HashMap, diff --git a/genesis/src/stakes.rs b/genesis/src/stakes.rs index db9104b7eb..ca2b8da4d9 100644 --- a/genesis/src/stakes.rs +++ b/genesis/src/stakes.rs @@ -1,15 +1,22 @@ //! stakes generator -use crate::{ - address_generator::AddressGenerator, - unlocks::{UnlockInfo, Unlocks}, -}; -use solana_sdk::{ - account::Account, clock::Slot, genesis_config::GenesisConfig, pubkey::Pubkey, system_program, - timing::years_as_slots, -}; -use solana_stake_program::{ - self, - stake_state::{create_lockup_stake_account, Authorized, Lockup, StakeState}, +use { + crate::{ + address_generator::AddressGenerator, + unlocks::{UnlockInfo, Unlocks}, + }, + solana_sdk::{ + account::Account, + clock::Slot, + genesis_config::GenesisConfig, + pubkey::Pubkey, + stake::{ + self, + state::{Authorized, Lockup, StakeState}, + }, + system_program, + timing::years_as_slots, + }, + solana_stake_program::stake_state::create_lockup_stake_account, }; #[derive(Debug)] @@ -98,8 +105,7 @@ pub fn create_and_add_stakes( genesis_config.ticks_per_slot, ); - let mut address_generator = - AddressGenerator::new(&authorized.staker, &solana_stake_program::id()); + let mut address_generator = AddressGenerator::new(&authorized.staker, &stake::program::id()); let stake_rent_reserve = StakeState::get_rent_exempt_reserve(&genesis_config.rent); diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 7b27026a6f..395ce3292b 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -44,9 +44,10 @@ use solana_sdk::{ pubkey::Pubkey, rent::Rent, shred_version::compute_shred_version, + stake::{self, state::StakeState}, system_program, }; -use solana_stake_program::stake_state::{self, PointValue, StakeState}; +use solana_stake_program::stake_state::{self, PointValue}; use solana_vote_program::{ self, vote_state::{self, VoteState}, @@ -2040,7 +2041,7 @@ fn main() { if remove_stake_accounts { for (address, mut account) in bank - .get_program_accounts(&solana_stake_program::id()) + .get_program_accounts(&stake::program::id()) .unwrap() .into_iter() { diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 9854e521b5..4d4499b452 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -45,7 +45,6 @@ solana-perf = { path = "../perf", version = "=1.8.0" } solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.8.0" } solana-runtime = { path = "../runtime", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.8.0" } solana-storage-proto = { path = "../storage-proto", version = "=1.8.0" } solana-vote-program = { path = "../programs/vote", version = "=1.8.0" } @@ -74,7 +73,6 @@ features = ["lz4"] assert_matches = "1.3.0" matches = "0.1.6" solana-account-decoder = { path = "../account-decoder", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } [build-dependencies] rustc_version = "0.4" diff --git a/ledger/src/staking_utils.rs b/ledger/src/staking_utils.rs index 7b7c24bb96..82d06ce368 100644 --- a/ledger/src/staking_utils.rs +++ b/ledger/src/staking_utils.rs @@ -74,13 +74,13 @@ pub(crate) mod tests { pubkey::Pubkey, signature::{Keypair, Signer}, signers::Signers, + stake::{ + instruction as stake_instruction, + state::{Authorized, Delegation, Lockup, Stake}, + }, sysvar::stake_history::{self, StakeHistory}, transaction::Transaction, }; - use solana_stake_program::{ - stake_instruction, - stake_state::{Authorized, Delegation, Lockup, Stake}, - }; use solana_vote_program::{ vote_instruction, vote_state::{VoteInit, VoteState, VoteStateVersions}, diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 080adae347..d455daefbb 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -29,13 +29,14 @@ use solana_sdk::{ poh_config::PohConfig, pubkey::Pubkey, signature::{Keypair, Signer}, + stake::{ + config as stake_config, instruction as stake_instruction, + state::{Authorized, Lockup}, + }, system_transaction, transaction::Transaction, }; -use solana_stake_program::{ - config as stake_config, stake_instruction, - stake_state::{Authorized, Lockup, StakeState}, -}; +use solana_stake_program::{config::create_account as create_stake_config_account, stake_state}; use solana_vote_program::{ vote_instruction, vote_state::{VoteInit, VoteState}, @@ -190,7 +191,7 @@ impl LocalCluster { // Replace staking config genesis_config.add_account( stake_config::id(), - stake_config::create_account( + create_stake_config_account( 1, &stake_config::Config { warmup_cooldown_rate: 1_000_000_000.0f64, @@ -568,7 +569,7 @@ impl LocalCluster { ) { (Ok(Some(stake_account)), Ok(Some(vote_account))) => { match ( - StakeState::stake_from(&stake_account), + stake_state::stake_from(&stake_account), VoteState::from(&vote_account), ) { (Some(stake_state), Some(vote_state)) => { diff --git a/local-cluster/tests/local_cluster.rs b/local-cluster/tests/local_cluster.rs index c227470fa6..0b22475ac4 100644 --- a/local-cluster/tests/local_cluster.rs +++ b/local-cluster/tests/local_cluster.rs @@ -1402,7 +1402,7 @@ fn test_mainnet_beta_cluster_type() { for program_id in [ &solana_config_program::id(), &solana_sdk::system_program::id(), - &solana_stake_program::id(), + &solana_sdk::stake::program::id(), &solana_vote_program::id(), &solana_sdk::bpf_loader_deprecated::id(), &solana_sdk::bpf_loader::id(), diff --git a/perf/Cargo.toml b/perf/Cargo.toml index 197bb75da3..fd3394b633 100644 --- a/perf/Cargo.toml +++ b/perf/Cargo.toml @@ -23,7 +23,6 @@ solana-logger = { path = "../logger", version = "=1.8.0" } solana-metrics = { path = "../metrics", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } [lib] name = "solana_perf" diff --git a/perf/src/test_tx.rs b/perf/src/test_tx.rs index a571ec9f85..a684531718 100644 --- a/perf/src/test_tx.rs +++ b/perf/src/test_tx.rs @@ -1,10 +1,12 @@ -use solana_sdk::hash::Hash; -use solana_sdk::instruction::CompiledInstruction; -use solana_sdk::signature::{Keypair, Signer}; -use solana_sdk::system_instruction::SystemInstruction; -use solana_sdk::system_program; -use solana_sdk::system_transaction; -use solana_sdk::transaction::Transaction; +use solana_sdk::{ + hash::Hash, + instruction::CompiledInstruction, + signature::{Keypair, Signer}, + stake, + system_instruction::SystemInstruction, + system_program, system_transaction, + transaction::Transaction, +}; pub fn test_tx() -> Transaction { let keypair1 = Keypair::new(); @@ -22,7 +24,7 @@ pub fn test_multisig_tx() -> Transaction { let transfer_instruction = SystemInstruction::Transfer { lamports }; - let program_ids = vec![system_program::id(), solana_stake_program::id()]; + let program_ids = vec![system_program::id(), stake::program::id()]; let instructions = vec![CompiledInstruction::new( 0, diff --git a/program-test/Cargo.toml b/program-test/Cargo.toml index e3dee598f4..0db187166f 100644 --- a/program-test/Cargo.toml +++ b/program-test/Cargo.toml @@ -29,4 +29,3 @@ tokio = { version = "1", features = ["full"] } [dev-dependencies] assert_matches = "1.3.0" -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } diff --git a/program-test/tests/warp.rs b/program-test/tests/warp.rs index 9ed46aac10..f3dee52f45 100644 --- a/program-test/tests/warp.rs +++ b/program-test/tests/warp.rs @@ -12,6 +12,10 @@ use { pubkey::Pubkey, rent::Rent, signature::{Keypair, Signer}, + stake::{ + instruction as stake_instruction, + state::{Authorized, Lockup, StakeState}, + }, system_instruction, system_program, sysvar::{ clock, @@ -20,10 +24,6 @@ use { }, transaction::{Transaction, TransactionError}, }, - solana_stake_program::{ - stake_instruction, - stake_state::{Authorized, Lockup, StakeState}, - }, solana_vote_program::{ vote_instruction, vote_state::{VoteInit, VoteState}, diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index b5de1411a2..0960002dab 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2730,7 +2730,6 @@ dependencies = [ "serde_json", "solana-config-program", "solana-sdk", - "solana-stake-program", "solana-vote-program", "spl-token", "thiserror", @@ -3143,7 +3142,6 @@ dependencies = [ "solana-clap-utils", "solana-client", "solana-sdk", - "solana-stake-program", "solana-transaction-status", "solana-vote-program", "spl-memo", @@ -3643,7 +3641,6 @@ dependencies = [ "solana-account-decoder", "solana-runtime", "solana-sdk", - "solana-stake-program", "solana-vote-program", "spl-associated-token-account", "spl-memo", diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 3a5806d091..1ceeb39f8f 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -1297,7 +1297,7 @@ fn assert_instruction_count() { ("solana_bpf_rust_external_spend", 504), ("solana_bpf_rust_iter", 724), ("solana_bpf_rust_many_args", 233), - ("solana_bpf_rust_mem", 3096), + ("solana_bpf_rust_mem", 3117), ("solana_bpf_rust_membuiltins", 4065), ("solana_bpf_rust_noop", 478), ("solana_bpf_rust_param_passing", 46), diff --git a/programs/config/src/lib.rs b/programs/config/src/lib.rs index e170ef7659..e18b8e24ed 100644 --- a/programs/config/src/lib.rs +++ b/programs/config/src/lib.rs @@ -9,6 +9,7 @@ use solana_sdk::{ account::{Account, AccountSharedData}, pubkey::Pubkey, short_vec, + stake::config::Config as StakeConfig, }; solana_sdk::declare_id!("Config1111111111111111111111111111111111111"); @@ -18,6 +19,13 @@ pub trait ConfigState: serde::Serialize + Default { fn max_space() -> u64; } +// TODO move ConfigState into `solana_program` to implement trait locally +impl ConfigState for StakeConfig { + fn max_space() -> u64 { + serialized_size(&StakeConfig::default()).unwrap() + } +} + /// A collection of keys to be stored in Config account data. #[derive(Debug, Default, Deserialize, Serialize)] pub struct ConfigKeys { diff --git a/programs/stake/src/config.rs b/programs/stake/src/config.rs index ee968d40c4..acd7416c2c 100644 --- a/programs/stake/src/config.rs +++ b/programs/stake/src/config.rs @@ -1,52 +1,36 @@ //! config for staking //! carries variables that the stake program cares about -use bincode::{deserialize, serialized_size}; -use serde_derive::{Deserialize, Serialize}; -use solana_config_program::{create_config_account, get_config_data, ConfigState}; +use bincode::deserialize; +use solana_config_program::{create_config_account, get_config_data}; use solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, genesis_config::GenesisConfig, instruction::InstructionError, keyed_account::KeyedAccount, + stake::config::{self, Config}, }; -// stake config ID -solana_sdk::declare_id!("StakeConfig11111111111111111111111111111111"); +#[deprecated( + since = "1.8.0", + note = "Please use `solana_sdk::stake::config` or `solana_program::stake::config` instead" +)] +pub use solana_sdk::stake::config::*; -// means that no more than RATE of current effective stake may be added or subtracted per -// epoch -pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25; -pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8; - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] -pub struct Config { - /// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake - pub warmup_cooldown_rate: f64, - /// percentage of stake lost when slash, expressed as a portion of std::u8::MAX - pub slash_penalty: u8, +pub fn from(account: &T) -> Option { + get_config_data(&account.data()) + .ok() + .and_then(|data| deserialize(data).ok()) } -impl Config { - pub fn from(account: &T) -> Option { - get_config_data(&account.data()) - .ok() - .and_then(|data| deserialize(data).ok()) +pub fn from_keyed_account(account: &KeyedAccount) -> Result { + if !config::check_id(account.unsigned_key()) { + return Err(InstructionError::InvalidArgument); } + from(&*account.try_account_ref()?).ok_or(InstructionError::InvalidArgument) } -impl Default for Config { - fn default() -> Self { - Self { - warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE, - slash_penalty: DEFAULT_SLASH_PENALTY, - } - } -} - -impl ConfigState for Config { - fn max_space() -> u64 { - serialized_size(&Config::default()).unwrap() - } +pub fn create_account(lamports: u64, config: &Config) -> AccountSharedData { + create_config_account(vec![], config, lamports) } pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 { @@ -55,22 +39,11 @@ pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 { account.set_lamports(lamports.max(1)); - genesis_config.add_account(id(), account); + genesis_config.add_account(config::id(), account); lamports } -pub fn create_account(lamports: u64, config: &Config) -> AccountSharedData { - create_config_account(vec![], config, lamports) -} - -pub fn from_keyed_account(account: &KeyedAccount) -> Result { - if !check_id(account.unsigned_key()) { - return Err(InstructionError::InvalidArgument); - } - Config::from(&*account.try_account_ref()?).ok_or(InstructionError::InvalidArgument) -} - #[cfg(test)] mod tests { use super::*; @@ -80,7 +53,7 @@ mod tests { #[test] fn test() { let account = RefCell::new(create_account(0, &Config::default())); - assert_eq!(Config::from(&account.borrow()), Some(Config::default())); + assert_eq!(from(&account.borrow()), Some(Config::default())); assert_eq!( from_keyed_account(&KeyedAccount::new(&Pubkey::default(), false, &account)), Err(InstructionError::InvalidArgument) diff --git a/programs/stake/src/lib.rs b/programs/stake/src/lib.rs index f93ed51656..1de5e41071 100644 --- a/programs/stake/src/lib.rs +++ b/programs/stake/src/lib.rs @@ -2,15 +2,16 @@ #![allow(clippy::integer_arithmetic)] use solana_sdk::genesis_config::GenesisConfig; +#[deprecated( + since = "1.8.0", + note = "Please use `solana_sdk::stake::program::id` or `solana_program::stake::program::id` instead" +)] +pub use solana_sdk::stake::program::{check_id, id}; + pub mod config; pub mod stake_instruction; pub mod stake_state; -solana_sdk::declare_id!("Stake11111111111111111111111111111111111111"); - pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig) -> u64 { config::add_genesis_account(genesis_config) } - -#[macro_use] -extern crate solana_frozen_abi_macro; diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index e89be82e7a..b80db4e5e2 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -1,481 +1,23 @@ -use crate::{ - config, id, - stake_state::{Authorized, Lockup, StakeAccount, StakeAuthorize, StakeState}, +use { + crate::{config, stake_state::StakeAccount}, + log::*, + solana_sdk::{ + feature_set, + instruction::InstructionError, + keyed_account::{from_keyed_account, get_signers, keyed_account_at_index}, + process_instruction::{get_sysvar, InvokeContext}, + program_utils::limited_deserialize, + pubkey::Pubkey, + stake::{instruction::StakeInstruction, program::id}, + sysvar::{self, clock::Clock, rent::Rent, stake_history::StakeHistory}, + }, }; -use log::*; -use num_derive::{FromPrimitive, ToPrimitive}; -use serde_derive::{Deserialize, Serialize}; -use solana_sdk::{ - clock::{Epoch, UnixTimestamp}, - decode_error::DecodeError, - feature_set, - instruction::{AccountMeta, Instruction, InstructionError}, - keyed_account::{from_keyed_account, get_signers, keyed_account_at_index}, - process_instruction::{get_sysvar, InvokeContext}, - program_utils::limited_deserialize, - pubkey::Pubkey, - system_instruction, - sysvar::{self, clock::Clock, rent::Rent, stake_history::StakeHistory}, -}; -use thiserror::Error; -/// Reasons the stake might have had an error -#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] -pub enum StakeError { - #[error("not enough credits to redeem")] - NoCreditsToRedeem, - - #[error("lockup has not yet expired")] - LockupInForce, - - #[error("stake already deactivated")] - AlreadyDeactivated, - - #[error("one re-delegation permitted per epoch")] - TooSoonToRedelegate, - - #[error("split amount is more than is staked")] - InsufficientStake, - - #[error("stake account with transient stake cannot be merged")] - MergeTransientStake, - - #[error("stake account merge failed due to different authority, lockups or state")] - MergeMismatch, - - #[error("custodian address not present")] - CustodianMissing, - - #[error("custodian signature not present")] - CustodianSignatureMissing, -} - -impl DecodeError for StakeError { - fn type_of() -> &'static str { - "StakeError" - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub enum StakeInstruction { - /// Initialize a stake with lockup and authorization information - /// - /// # Account references - /// 0. [WRITE] Uninitialized stake account - /// 1. [] Rent sysvar - /// - /// Authorized carries pubkeys that must sign staker transactions - /// and withdrawer transactions. - /// Lockup carries information about withdrawal restrictions - Initialize(Authorized, Lockup), - - /// Authorize a key to manage stake or withdrawal - /// - /// # Account references - /// 0. [WRITE] Stake account to be updated - /// 1. [] Clock sysvar - /// 2. [SIGNER] The stake or withdraw authority - /// 3. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before - /// lockup expiration - Authorize(Pubkey, StakeAuthorize), - - /// Delegate a stake to a particular vote account - /// - /// # Account references - /// 0. [WRITE] Initialized stake account to be delegated - /// 1. [] Vote account to which this stake will be delegated - /// 2. [] Clock sysvar - /// 3. [] Stake history sysvar that carries stake warmup/cooldown history - /// 4. [] Address of config account that carries stake config - /// 5. [SIGNER] Stake authority - /// - /// The entire balance of the staking account is staked. DelegateStake - /// can be called multiple times, but re-delegation is delayed - /// by one epoch - DelegateStake, - - /// Split u64 tokens and stake off a stake account into another stake account. - /// - /// # Account references - /// 0. [WRITE] Stake account to be split; must be in the Initialized or Stake state - /// 1. [WRITE] Uninitialized stake account that will take the split-off amount - /// 2. [SIGNER] Stake authority - Split(u64), - - /// Withdraw unstaked lamports from the stake account - /// - /// # Account references - /// 0. [WRITE] Stake account from which to withdraw - /// 1. [WRITE] Recipient account - /// 2. [] Clock sysvar - /// 3. [] Stake history sysvar that carries stake warmup/cooldown history - /// 4. [SIGNER] Withdraw authority - /// 5. Optional: [SIGNER] Lockup authority, if before lockup expiration - /// - /// The u64 is the portion of the stake account balance to be withdrawn, - /// must be `<= StakeAccount.lamports - staked_lamports`. - Withdraw(u64), - - /// Deactivates the stake in the account - /// - /// # Account references - /// 0. [WRITE] Delegated stake account - /// 1. [] Clock sysvar - /// 2. [SIGNER] Stake authority - Deactivate, - - /// Set stake lockup - /// - /// If a lockup is not active, the withdraw authority may set a new lockup - /// If a lockup is active, the lockup custodian may update the lockup parameters - /// - /// # Account references - /// 0. [WRITE] Initialized stake account - /// 1. [SIGNER] Lockup authority or withdraw authority - SetLockup(LockupArgs), - - /// Merge two stake accounts. - /// - /// Both accounts must have identical lockup and authority keys. A merge - /// is possible between two stakes in the following states with no additional - /// conditions: - /// - /// * two deactivated stakes - /// * an inactive stake into an activating stake during its activation epoch - /// - /// For the following cases, the voter pubkey and vote credits observed must match: - /// - /// * two activated stakes - /// * two activating accounts that share an activation epoch, during the activation epoch - /// - /// All other combinations of stake states will fail to merge, including all - /// "transient" states, where a stake is activating or deactivating with a - /// non-zero effective stake. - /// - /// # Account references - /// 0. [WRITE] Destination stake account for the merge - /// 1. [WRITE] Source stake account for to merge. This account will be drained - /// 2. [] Clock sysvar - /// 3. [] Stake history sysvar that carries stake warmup/cooldown history - /// 4. [SIGNER] Stake authority - Merge, - - /// Authorize a key to manage stake or withdrawal with a derived key - /// - /// # Account references - /// 0. [WRITE] Stake account to be updated - /// 1. [SIGNER] Base key of stake or withdraw authority - /// 2. [] Clock sysvar - /// 3. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before - /// lockup expiration - AuthorizeWithSeed(AuthorizeWithSeedArgs), -} - -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] -pub struct LockupArgs { - pub unix_timestamp: Option, - pub epoch: Option, - pub custodian: Option, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] -pub struct AuthorizeWithSeedArgs { - pub new_authorized_pubkey: Pubkey, - pub stake_authorize: StakeAuthorize, - pub authority_seed: String, - pub authority_owner: Pubkey, -} - -pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction { - Instruction::new_with_bincode( - id(), - &StakeInstruction::Initialize(*authorized, *lockup), - vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new_readonly(sysvar::rent::id(), false), - ], - ) -} - -pub fn create_account_with_seed( - from_pubkey: &Pubkey, - stake_pubkey: &Pubkey, - base: &Pubkey, - seed: &str, - authorized: &Authorized, - lockup: &Lockup, - lamports: u64, -) -> Vec { - vec![ - system_instruction::create_account_with_seed( - from_pubkey, - stake_pubkey, - base, - seed, - lamports, - std::mem::size_of::() as u64, - &id(), - ), - initialize(stake_pubkey, authorized, lockup), - ] -} - -pub fn create_account( - from_pubkey: &Pubkey, - stake_pubkey: &Pubkey, - authorized: &Authorized, - lockup: &Lockup, - lamports: u64, -) -> Vec { - vec![ - system_instruction::create_account( - from_pubkey, - stake_pubkey, - lamports, - std::mem::size_of::() as u64, - &id(), - ), - initialize(stake_pubkey, authorized, lockup), - ] -} - -fn _split( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - lamports: u64, - split_stake_pubkey: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new(*split_stake_pubkey, false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - - Instruction::new_with_bincode(id(), &StakeInstruction::Split(lamports), account_metas) -} - -pub fn split( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - lamports: u64, - split_stake_pubkey: &Pubkey, -) -> Vec { - vec![ - system_instruction::allocate(split_stake_pubkey, std::mem::size_of::() as u64), - system_instruction::assign(split_stake_pubkey, &id()), - _split( - stake_pubkey, - authorized_pubkey, - lamports, - split_stake_pubkey, - ), - ] -} - -pub fn split_with_seed( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - lamports: u64, - split_stake_pubkey: &Pubkey, // derived using create_with_seed() - base: &Pubkey, // base - seed: &str, // seed -) -> Vec { - vec![ - system_instruction::allocate_with_seed( - split_stake_pubkey, - base, - seed, - std::mem::size_of::() as u64, - &id(), - ), - _split( - stake_pubkey, - authorized_pubkey, - lamports, - split_stake_pubkey, - ), - ] -} - -pub fn merge( - destination_stake_pubkey: &Pubkey, - source_stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, -) -> Vec { - let account_metas = vec![ - AccountMeta::new(*destination_stake_pubkey, false), - AccountMeta::new(*source_stake_pubkey, false), - AccountMeta::new_readonly(sysvar::clock::id(), false), - AccountMeta::new_readonly(sysvar::stake_history::id(), false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - - vec![Instruction::new_with_bincode( - id(), - &StakeInstruction::Merge, - account_metas, - )] -} - -pub fn create_account_and_delegate_stake( - from_pubkey: &Pubkey, - stake_pubkey: &Pubkey, - vote_pubkey: &Pubkey, - authorized: &Authorized, - lockup: &Lockup, - lamports: u64, -) -> Vec { - let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, lamports); - instructions.push(delegate_stake( - stake_pubkey, - &authorized.staker, - vote_pubkey, - )); - instructions -} - -pub fn create_account_with_seed_and_delegate_stake( - from_pubkey: &Pubkey, - stake_pubkey: &Pubkey, - base: &Pubkey, - seed: &str, - vote_pubkey: &Pubkey, - authorized: &Authorized, - lockup: &Lockup, - lamports: u64, -) -> Vec { - let mut instructions = create_account_with_seed( - from_pubkey, - stake_pubkey, - base, - seed, - authorized, - lockup, - lamports, - ); - instructions.push(delegate_stake( - stake_pubkey, - &authorized.staker, - vote_pubkey, - )); - instructions -} - -pub fn authorize( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - new_authorized_pubkey: &Pubkey, - stake_authorize: StakeAuthorize, - custodian_pubkey: Option<&Pubkey>, -) -> Instruction { - let mut account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new_readonly(sysvar::clock::id(), false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - - if let Some(custodian_pubkey) = custodian_pubkey { - account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true)); - } - - Instruction::new_with_bincode( - id(), - &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize), - account_metas, - ) -} - -pub fn authorize_with_seed( - stake_pubkey: &Pubkey, - authority_base: &Pubkey, - authority_seed: String, - authority_owner: &Pubkey, - new_authorized_pubkey: &Pubkey, - stake_authorize: StakeAuthorize, - custodian_pubkey: Option<&Pubkey>, -) -> Instruction { - let mut account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new_readonly(*authority_base, true), - AccountMeta::new_readonly(sysvar::clock::id(), false), - ]; - - if let Some(custodian_pubkey) = custodian_pubkey { - account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true)); - } - - let args = AuthorizeWithSeedArgs { - new_authorized_pubkey: *new_authorized_pubkey, - stake_authorize, - authority_seed, - authority_owner: *authority_owner, - }; - - Instruction::new_with_bincode( - id(), - &StakeInstruction::AuthorizeWithSeed(args), - account_metas, - ) -} - -pub fn delegate_stake( - stake_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - vote_pubkey: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new_readonly(*vote_pubkey, false), - AccountMeta::new_readonly(sysvar::clock::id(), false), - AccountMeta::new_readonly(sysvar::stake_history::id(), false), - AccountMeta::new_readonly(crate::config::id(), false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - Instruction::new_with_bincode(id(), &StakeInstruction::DelegateStake, account_metas) -} - -pub fn withdraw( - stake_pubkey: &Pubkey, - withdrawer_pubkey: &Pubkey, - to_pubkey: &Pubkey, - lamports: u64, - custodian_pubkey: Option<&Pubkey>, -) -> Instruction { - let mut account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new(*to_pubkey, false), - AccountMeta::new_readonly(sysvar::clock::id(), false), - AccountMeta::new_readonly(sysvar::stake_history::id(), false), - AccountMeta::new_readonly(*withdrawer_pubkey, true), - ]; - - if let Some(custodian_pubkey) = custodian_pubkey { - account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true)); - } - - Instruction::new_with_bincode(id(), &StakeInstruction::Withdraw(lamports), account_metas) -} - -pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new_readonly(sysvar::clock::id(), false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas) -} - -pub fn set_lockup( - stake_pubkey: &Pubkey, - lockup: &LockupArgs, - custodian_pubkey: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new_readonly(*custodian_pubkey, true), - ]; - Instruction::new_with_bincode(id(), &StakeInstruction::SetLockup(*lockup), account_metas) -} +#[deprecated( + since = "1.8.0", + note = "Please use `solana_sdk::stake::instruction` or `solana_program::stake::instruction` instead" +)] +pub use solana_sdk::stake::instruction::*; pub fn process_instruction( _program_id: &Pubkey, @@ -637,9 +179,15 @@ mod tests { use bincode::serialize; use solana_sdk::{ account::{self, Account, AccountSharedData, WritableAccount}, + instruction::Instruction, keyed_account::KeyedAccount, process_instruction::{mock_set_sysvar, MockInvokeContext}, rent::Rent, + stake::{ + config as stake_config, + instruction::{self, LockupArgs}, + state::{Authorized, Lockup, StakeAuthorize}, + }, sysvar::stake_history::StakeHistory, }; use std::cell::RefCell; @@ -685,8 +233,8 @@ mod tests { )) } else if sysvar::stake_history::check_id(&meta.pubkey) { account::create_account_shared_data_for_test(&StakeHistory::default()) - } else if config::check_id(&meta.pubkey) { - config::create_account(0, &config::Config::default()) + } else if stake_config::check_id(&meta.pubkey) { + config::create_account(0, &stake_config::Config::default()) } else if sysvar::rent::check_id(&meta.pubkey) { account::create_account_shared_data_for_test(&Rent::default()) } else if meta.pubkey == invalid_stake_state_pubkey() { @@ -735,7 +283,7 @@ mod tests { #[test] fn test_stake_process_instruction() { assert_eq!( - process_instruction(&initialize( + process_instruction(&instruction::initialize( &Pubkey::default(), &Authorized::default(), &Lockup::default() @@ -743,7 +291,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&authorize( + process_instruction(&instruction::authorize( &Pubkey::default(), &Pubkey::default(), &Pubkey::default(), @@ -754,7 +302,7 @@ mod tests { ); assert_eq!( process_instruction( - &split( + &instruction::split( &Pubkey::default(), &Pubkey::default(), 100, @@ -765,7 +313,7 @@ mod tests { ); assert_eq!( process_instruction( - &merge( + &instruction::merge( &Pubkey::default(), &invalid_stake_state_pubkey(), &Pubkey::default(), @@ -775,7 +323,7 @@ mod tests { ); assert_eq!( process_instruction( - &split_with_seed( + &instruction::split_with_seed( &Pubkey::default(), &Pubkey::default(), 100, @@ -787,7 +335,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&delegate_stake( + process_instruction(&instruction::delegate_stake( &Pubkey::default(), &Pubkey::default(), &invalid_vote_state_pubkey(), @@ -795,7 +343,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&withdraw( + process_instruction(&instruction::withdraw( &Pubkey::default(), &Pubkey::default(), &solana_sdk::pubkey::new_rand(), @@ -805,11 +353,14 @@ mod tests { Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&deactivate_stake(&Pubkey::default(), &Pubkey::default())), + process_instruction(&instruction::deactivate_stake( + &Pubkey::default(), + &Pubkey::default() + )), Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&set_lockup( + process_instruction(&instruction::set_lockup( &Pubkey::default(), &LockupArgs::default(), &Pubkey::default() @@ -821,7 +372,7 @@ mod tests { #[test] fn test_spoofed_stake_accounts() { assert_eq!( - process_instruction(&initialize( + process_instruction(&instruction::initialize( &spoofed_stake_state_pubkey(), &Authorized::default(), &Lockup::default() @@ -829,7 +380,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); assert_eq!( - process_instruction(&authorize( + process_instruction(&instruction::authorize( &spoofed_stake_state_pubkey(), &Pubkey::default(), &Pubkey::default(), @@ -840,7 +391,7 @@ mod tests { ); assert_eq!( process_instruction( - &split( + &instruction::split( &spoofed_stake_state_pubkey(), &Pubkey::default(), 100, @@ -851,7 +402,7 @@ mod tests { ); assert_eq!( process_instruction( - &split( + &instruction::split( &Pubkey::default(), &Pubkey::default(), 100, @@ -862,7 +413,7 @@ mod tests { ); assert_eq!( process_instruction( - &merge( + &instruction::merge( &spoofed_stake_state_pubkey(), &Pubkey::default(), &Pubkey::default(), @@ -872,7 +423,7 @@ mod tests { ); assert_eq!( process_instruction( - &merge( + &instruction::merge( &Pubkey::default(), &spoofed_stake_state_pubkey(), &Pubkey::default(), @@ -882,7 +433,7 @@ mod tests { ); assert_eq!( process_instruction( - &split_with_seed( + &instruction::split_with_seed( &spoofed_stake_state_pubkey(), &Pubkey::default(), 100, @@ -894,7 +445,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); assert_eq!( - process_instruction(&delegate_stake( + process_instruction(&instruction::delegate_stake( &spoofed_stake_state_pubkey(), &Pubkey::default(), &Pubkey::default(), @@ -902,7 +453,7 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); assert_eq!( - process_instruction(&withdraw( + process_instruction(&instruction::withdraw( &spoofed_stake_state_pubkey(), &Pubkey::default(), &solana_sdk::pubkey::new_rand(), @@ -912,14 +463,14 @@ mod tests { Err(InstructionError::InvalidAccountOwner), ); assert_eq!( - process_instruction(&deactivate_stake( + process_instruction(&instruction::deactivate_stake( &spoofed_stake_state_pubkey(), &Pubkey::default() )), Err(InstructionError::InvalidAccountOwner), ); assert_eq!( - process_instruction(&set_lockup( + process_instruction(&instruction::set_lockup( &spoofed_stake_state_pubkey(), &LockupArgs::default(), &Pubkey::default() @@ -1051,8 +602,9 @@ mod tests { let stake_history_account = RefCell::new(account::create_account_shared_data_for_test( &sysvar::stake_history::StakeHistory::default(), )); - let config_address = config::id(); - let config_account = RefCell::new(config::create_account(0, &config::Config::default())); + let config_address = stake_config::id(); + let config_account = + RefCell::new(config::create_account(0, &stake_config::Config::default())); let keyed_accounts = vec![ KeyedAccount::new(&stake_address, true, &stake_account), KeyedAccount::new(&vote_address, false, &bad_vote_account), @@ -1140,30 +692,4 @@ mod tests { Err(InstructionError::NotEnoughAccountKeys), ); } - - #[test] - fn test_custom_error_decode() { - use num_traits::FromPrimitive; - fn pretty_err(err: InstructionError) -> String - where - T: 'static + std::error::Error + DecodeError + FromPrimitive, - { - if let InstructionError::Custom(code) = err { - let specific_error: T = T::decode_custom_error_to_enum(code).unwrap(); - format!( - "{:?}: {}::{:?} - {}", - err, - T::type_of(), - specific_error, - specific_error, - ) - } else { - "".to_string() - } - } - assert_eq!( - "Custom(0): StakeError::NoCreditsToRedeem - not enough credits to redeem", - pretty_err::(StakeError::NoCreditsToRedeem.into()) - ) - } } diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 5e9e41a27e..55689679f0 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -3,35 +3,34 @@ //! * keep track of rewards //! * own mining pools -use crate::{ - config::Config, - id, - stake_instruction::{LockupArgs, StakeError}, +use { + solana_sdk::{ + account::{AccountSharedData, ReadableAccount, WritableAccount}, + account_utils::{State, StateMut}, + clock::{Clock, Epoch}, + ic_msg, + instruction::{checked_add, InstructionError}, + keyed_account::KeyedAccount, + process_instruction::InvokeContext, + pubkey::Pubkey, + rent::{Rent, ACCOUNT_STORAGE_OVERHEAD}, + stake::{ + config::Config, + instruction::{LockupArgs, StakeError}, + program::id, + state::{Authorized, Delegation, Lockup, Meta, Stake, StakeAuthorize, StakeState}, + }, + stake_history::{StakeHistory, StakeHistoryEntry}, + }, + solana_vote_program::vote_state::{VoteState, VoteStateVersions}, + std::{collections::HashSet, convert::TryFrom}, }; -use serde_derive::{Deserialize, Serialize}; -use solana_sdk::{ - account::{AccountSharedData, ReadableAccount, WritableAccount}, - account_utils::{State, StateMut}, - clock::{Clock, Epoch, UnixTimestamp}, - ic_msg, - instruction::{checked_add, InstructionError}, - keyed_account::KeyedAccount, - process_instruction::InvokeContext, - pubkey::Pubkey, - rent::{Rent, ACCOUNT_STORAGE_OVERHEAD}, - stake_history::{StakeHistory, StakeHistoryEntry}, -}; -use solana_vote_program::vote_state::{VoteState, VoteStateVersions}; -use std::{collections::HashSet, convert::TryFrom}; -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] -#[allow(clippy::large_enum_variant)] -pub enum StakeState { - Uninitialized, - Initialized(Meta), - Stake(Meta, Stake), - RewardsPool, -} +#[deprecated( + since = "1.8.0", + note = "Please use `solana_sdk::stake::state` or `solana_program::stake::state` instead" +)] +pub use solana_sdk::stake::state::*; #[derive(Debug)] pub enum SkippedReason { @@ -64,489 +63,85 @@ pub(crate) fn null_tracer() -> Option } -impl Default for StakeState { - fn default() -> Self { - StakeState::Uninitialized - } +// utility function, used by Stakes, tests +pub fn from>(account: &T) -> Option { + account.state().ok() } -impl StakeState { - pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 { - rent.minimum_balance(std::mem::size_of::()) - } - - // utility function, used by Stakes, tests - pub fn from>(account: &T) -> Option { - account.state().ok() - } - - pub fn stake_from>(account: &T) -> Option { - Self::from(account).and_then(|state: Self| state.stake()) - } - pub fn stake(&self) -> Option { - match self { - StakeState::Stake(_meta, stake) => Some(*stake), - _ => None, - } - } - - pub fn delegation_from(account: &AccountSharedData) -> Option { - Self::from(account).and_then(|state: Self| state.delegation()) - } - pub fn delegation(&self) -> Option { - match self { - StakeState::Stake(_meta, stake) => Some(stake.delegation), - _ => None, - } - } - - pub fn authorized_from(account: &AccountSharedData) -> Option { - Self::from(account).and_then(|state: Self| state.authorized()) - } - - pub fn authorized(&self) -> Option { - match self { - StakeState::Stake(meta, _stake) => Some(meta.authorized), - StakeState::Initialized(meta) => Some(meta.authorized), - _ => None, - } - } - - pub fn lockup_from>(account: &T) -> Option { - Self::from(account).and_then(|state: Self| state.lockup()) - } - - pub fn lockup(&self) -> Option { - self.meta().map(|meta| meta.lockup) - } - - pub fn meta_from(account: &AccountSharedData) -> Option { - Self::from(account).and_then(|state: Self| state.meta()) - } - - pub fn meta(&self) -> Option { - match self { - StakeState::Stake(meta, _stake) => Some(*meta), - StakeState::Initialized(meta) => Some(*meta), - _ => None, - } - } +pub fn stake_from>(account: &T) -> Option { + from(account).and_then(|state: StakeState| state.stake()) } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] -pub enum StakeAuthorize { - Staker, - Withdrawer, +pub fn delegation_from(account: &AccountSharedData) -> Option { + from(account).and_then(|state: StakeState| state.delegation()) } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] -pub struct Lockup { - /// UnixTimestamp at which this stake will allow withdrawal, unless the - /// transaction is signed by the custodian - pub unix_timestamp: UnixTimestamp, - /// epoch height at which this stake will allow withdrawal, unless the - /// transaction is signed by the custodian - pub epoch: Epoch, - /// custodian signature on a transaction exempts the operation from - /// lockup constraints - pub custodian: Pubkey, +pub fn authorized_from(account: &AccountSharedData) -> Option { + from(account).and_then(|state: StakeState| state.authorized()) } -impl Lockup { - pub fn is_in_force(&self, clock: &Clock, custodian: Option<&Pubkey>) -> bool { - if custodian == Some(&self.custodian) { - return false; - } - self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch - } +pub fn lockup_from>(account: &T) -> Option { + from(account).and_then(|state: StakeState| state.lockup()) } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] -pub struct Authorized { - pub staker: Pubkey, - pub withdrawer: Pubkey, +pub fn meta_from(account: &AccountSharedData) -> Option { + from(account).and_then(|state: StakeState| state.meta()) } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] -pub struct Meta { - pub rent_exempt_reserve: u64, - pub authorized: Authorized, - pub lockup: Lockup, -} - -impl Meta { - pub fn set_lockup( - &mut self, - lockup: &LockupArgs, - signers: &HashSet, - clock: Option<&Clock>, - ) -> Result<(), InstructionError> { - match clock { - None => { - // pre-stake_program_v4 behavior: custodian can set lockups at any time - if !signers.contains(&self.lockup.custodian) { - return Err(InstructionError::MissingRequiredSignature); - } - } - Some(clock) => { - // post-stake_program_v4 behavior: - // * custodian can update the lockup while in force - // * withdraw authority can set a new lockup - // - if self.lockup.is_in_force(clock, None) { - if !signers.contains(&self.lockup.custodian) { - return Err(InstructionError::MissingRequiredSignature); - } - } else if !signers.contains(&self.authorized.withdrawer) { - return Err(InstructionError::MissingRequiredSignature); - } - } - } - if let Some(unix_timestamp) = lockup.unix_timestamp { - self.lockup.unix_timestamp = unix_timestamp; - } - if let Some(epoch) = lockup.epoch { - self.lockup.epoch = epoch; - } - if let Some(custodian) = lockup.custodian { - self.lockup.custodian = custodian; - } - Ok(()) - } - - pub fn rewrite_rent_exempt_reserve( - &mut self, - rent: &Rent, - data_len: usize, - ) -> Option<(u64, u64)> { - let corrected_rent_exempt_reserve = rent.minimum_balance(data_len); - if corrected_rent_exempt_reserve != self.rent_exempt_reserve { - // We forcibly update rent_excempt_reserve even - // if rent_exempt_reserve > account_balance, hoping user might restore - // rent_exempt status by depositing. - let (old, new) = (self.rent_exempt_reserve, corrected_rent_exempt_reserve); - self.rent_exempt_reserve = corrected_rent_exempt_reserve; - Some((old, new)) +fn redelegate( + stake: &mut Stake, + stake_lamports: u64, + voter_pubkey: &Pubkey, + vote_state: &VoteState, + clock: &Clock, + stake_history: &StakeHistory, + config: &Config, + can_reverse_deactivation: bool, +) -> Result<(), StakeError> { + // If stake is currently active: + if stake.stake(clock.epoch, Some(stake_history), true) != 0 { + // If pubkey of new voter is the same as current, + // and we are scheduled to start deactivating this epoch, + // we rescind deactivation + if stake.delegation.voter_pubkey == *voter_pubkey + && clock.epoch == stake.delegation.deactivation_epoch + && can_reverse_deactivation + { + stake.delegation.deactivation_epoch = std::u64::MAX; + return Ok(()); } else { - None + // can't redelegate to another pubkey if stake is active. + return Err(StakeError::TooSoonToRedelegate); } } + // Either the stake is freshly activated, is active but has been + // deactivated this epoch, or has fully de-activated. + // Redelegation implies either re-activation or un-deactivation + + stake.delegation.stake = stake_lamports; + stake.delegation.activation_epoch = clock.epoch; + stake.delegation.deactivation_epoch = std::u64::MAX; + stake.delegation.voter_pubkey = *voter_pubkey; + stake.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate; + stake.credits_observed = vote_state.credits(); + Ok(()) } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] -pub struct Delegation { - /// to whom the stake is delegated - pub voter_pubkey: Pubkey, - /// activated stake amount, set at delegate() time - pub stake: u64, - /// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake - pub activation_epoch: Epoch, - /// epoch the stake was deactivated, std::Epoch::MAX if not deactivated - pub deactivation_epoch: Epoch, - /// how much stake we can activate per-epoch as a fraction of currently effective stake - pub warmup_cooldown_rate: f64, -} - -impl Default for Delegation { - fn default() -> Self { - Self { - voter_pubkey: Pubkey::default(), - stake: 0, - activation_epoch: 0, - deactivation_epoch: std::u64::MAX, - warmup_cooldown_rate: Config::default().warmup_cooldown_rate, - } - } -} - -impl Delegation { - pub fn new( - voter_pubkey: &Pubkey, - stake: u64, - activation_epoch: Epoch, - warmup_cooldown_rate: f64, - ) -> Self { - Self { - voter_pubkey: *voter_pubkey, +fn new_stake( + stake: u64, + voter_pubkey: &Pubkey, + vote_state: &VoteState, + activation_epoch: Epoch, + config: &Config, +) -> Stake { + Stake { + delegation: Delegation::new( + voter_pubkey, stake, activation_epoch, - warmup_cooldown_rate, - ..Delegation::default() - } - } - pub fn is_bootstrap(&self) -> bool { - self.activation_epoch == std::u64::MAX - } - - pub fn stake( - &self, - epoch: Epoch, - history: Option<&StakeHistory>, - fix_stake_deactivate: bool, - ) -> u64 { - self.stake_activating_and_deactivating(epoch, history, fix_stake_deactivate) - .0 - } - - // returned tuple is (effective, activating, deactivating) stake - #[allow(clippy::comparison_chain)] - pub fn stake_activating_and_deactivating( - &self, - target_epoch: Epoch, - history: Option<&StakeHistory>, - fix_stake_deactivate: bool, - ) -> (u64, u64, u64) { - let delegated_stake = self.stake; - - // first, calculate an effective and activating stake - let (effective_stake, activating_stake) = - self.stake_and_activating(target_epoch, history, fix_stake_deactivate); - - // then de-activate some portion if necessary - if target_epoch < self.deactivation_epoch { - // not deactivated - (effective_stake, activating_stake, 0) - } else if target_epoch == self.deactivation_epoch { - // can only deactivate what's activated - (effective_stake, 0, effective_stake.min(delegated_stake)) - } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = - history.and_then(|history| { - history - .get(&self.deactivation_epoch) - .map(|cluster_stake_at_deactivation_epoch| { - ( - history, - self.deactivation_epoch, - cluster_stake_at_deactivation_epoch, - ) - }) - }) - { - // target_epoch > self.deactivation_epoch - - // loop from my deactivation epoch until the target epoch - // current effective stake is updated using its previous epoch's cluster stake - let mut current_epoch; - let mut current_effective_stake = effective_stake; - loop { - current_epoch = prev_epoch + 1; - // if there is no deactivating stake at prev epoch, we should have been - // fully undelegated at this moment - if prev_cluster_stake.deactivating == 0 { - break; - } - - // I'm trying to get to zero, how much of the deactivation in stake - // this account is entitled to take - let weight = - current_effective_stake as f64 / prev_cluster_stake.deactivating as f64; - - // portion of newly not-effective cluster stake I'm entitled to at current epoch - let newly_not_effective_cluster_stake = - prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate; - let newly_not_effective_stake = - ((weight * newly_not_effective_cluster_stake) as u64).max(1); - - current_effective_stake = - current_effective_stake.saturating_sub(newly_not_effective_stake); - if current_effective_stake == 0 { - break; - } - - if current_epoch >= target_epoch { - break; - } - if let Some(current_cluster_stake) = history.get(¤t_epoch) { - prev_epoch = current_epoch; - prev_cluster_stake = current_cluster_stake; - } else { - break; - } - } - - // deactivating stake should equal to all of currently remaining effective stake - (current_effective_stake, 0, current_effective_stake) - } else { - // no history or I've dropped out of history, so assume fully deactivated - (0, 0, 0) - } - } - - // returned tuple is (effective, activating) stake - fn stake_and_activating( - &self, - target_epoch: Epoch, - history: Option<&StakeHistory>, - fix_stake_deactivate: bool, - ) -> (u64, u64) { - let delegated_stake = self.stake; - - if self.is_bootstrap() { - // fully effective immediately - (delegated_stake, 0) - } else if fix_stake_deactivate && self.activation_epoch == self.deactivation_epoch { - // activated but instantly deactivated; no stake at all regardless of target_epoch - // this must be after the bootstrap check and before all-is-activating check - (0, 0) - } else if target_epoch == self.activation_epoch { - // all is activating - (0, delegated_stake) - } else if target_epoch < self.activation_epoch { - // not yet enabled - (0, 0) - } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = - history.and_then(|history| { - history - .get(&self.activation_epoch) - .map(|cluster_stake_at_activation_epoch| { - ( - history, - self.activation_epoch, - cluster_stake_at_activation_epoch, - ) - }) - }) - { - // target_epoch > self.activation_epoch - - // loop from my activation epoch until the target epoch summing up my entitlement - // current effective stake is updated using its previous epoch's cluster stake - let mut current_epoch; - let mut current_effective_stake = 0; - loop { - current_epoch = prev_epoch + 1; - // if there is no activating stake at prev epoch, we should have been - // fully effective at this moment - if prev_cluster_stake.activating == 0 { - break; - } - - // how much of the growth in stake this account is - // entitled to take - let remaining_activating_stake = delegated_stake - current_effective_stake; - let weight = - remaining_activating_stake as f64 / prev_cluster_stake.activating as f64; - - // portion of newly effective cluster stake I'm entitled to at current epoch - let newly_effective_cluster_stake = - prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate; - let newly_effective_stake = - ((weight * newly_effective_cluster_stake) as u64).max(1); - - current_effective_stake += newly_effective_stake; - if current_effective_stake >= delegated_stake { - current_effective_stake = delegated_stake; - break; - } - - if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch { - break; - } - if let Some(current_cluster_stake) = history.get(¤t_epoch) { - prev_epoch = current_epoch; - prev_cluster_stake = current_cluster_stake; - } else { - break; - } - } - - ( - current_effective_stake, - delegated_stake - current_effective_stake, - ) - } else { - // no history or I've dropped out of history, so assume fully effective - (delegated_stake, 0) - } - } - - pub(crate) fn rewrite_stake( - &mut self, - account_balance: u64, - rent_exempt_balance: u64, - ) -> Option<(u64, u64)> { - // note that this will intentionally overwrite innocent - // deactivated-then-immeditealy-withdrawn stake accounts as well - // this is chosen to minimize the risks from complicated logic, - // over some unneeded rewrites - let corrected_stake = account_balance.saturating_sub(rent_exempt_balance); - if self.stake != corrected_stake { - // this could result in creating a 0-staked account; - // rewards and staking calc can handle it. - let (old, new) = (self.stake, corrected_stake); - self.stake = corrected_stake; - Some((old, new)) - } else { - None - } - } -} - -#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] -pub struct Stake { - pub delegation: Delegation, - /// credits observed is credits from vote account state when delegated or redeemed - pub credits_observed: u64, -} - -impl Authorized { - pub fn auto(authorized: &Pubkey) -> Self { - Self { - staker: *authorized, - withdrawer: *authorized, - } - } - pub fn check( - &self, - signers: &HashSet, - stake_authorize: StakeAuthorize, - ) -> Result<(), InstructionError> { - match stake_authorize { - StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()), - StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()), - _ => Err(InstructionError::MissingRequiredSignature), - } - } - - pub fn authorize( - &mut self, - signers: &HashSet, - new_authorized: &Pubkey, - stake_authorize: StakeAuthorize, - lockup_custodian_args: Option<(&Lockup, &Clock, Option<&Pubkey>)>, - ) -> Result<(), InstructionError> { - match stake_authorize { - StakeAuthorize::Staker => { - // Allow either the staker or the withdrawer to change the staker key - if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) { - return Err(InstructionError::MissingRequiredSignature); - } - self.staker = *new_authorized - } - StakeAuthorize::Withdrawer => { - if let Some((lockup, clock, custodian)) = lockup_custodian_args { - if lockup.is_in_force(&clock, None) { - match custodian { - None => { - return Err(StakeError::CustodianMissing.into()); - } - Some(custodian) => { - if !signers.contains(custodian) { - return Err(StakeError::CustodianSignatureMissing.into()); - } - - if lockup.is_in_force(&clock, Some(custodian)) { - return Err(StakeError::LockupInForce.into()); - } - } - } - } - } - self.check(signers, stake_authorize)?; - self.withdrawer = *new_authorized - } - } - Ok(()) + config.warmup_cooldown_rate, + ), + credits_observed: vote_state.credits(), } } @@ -560,296 +155,203 @@ pub struct PointValue { pub points: u128, // over these points } -impl Stake { - pub fn stake( - &self, - epoch: Epoch, - history: Option<&StakeHistory>, - fix_stake_deactivate: bool, - ) -> u64 { - self.delegation.stake(epoch, history, fix_stake_deactivate) +fn redeem_stake_rewards( + stake: &mut Stake, + point_value: &PointValue, + vote_state: &VoteState, + stake_history: Option<&StakeHistory>, + inflation_point_calc_tracer: &mut Option, + fix_stake_deactivate: bool, +) -> Option<(u64, u64)> { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { + inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved( + stake.credits_observed, + None, + )); } - - pub fn redeem_rewards( - &mut self, - point_value: &PointValue, - vote_state: &VoteState, - stake_history: Option<&StakeHistory>, - inflation_point_calc_tracer: &mut Option, - fix_stake_deactivate: bool, - ) -> Option<(u64, u64)> { + calculate_stake_rewards( + stake, + point_value, + vote_state, + stake_history, + inflation_point_calc_tracer, + fix_stake_deactivate, + ) + .map(|(stakers_reward, voters_reward, credits_observed)| { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved( - self.credits_observed, - None, + stake.credits_observed, + Some(credits_observed), )); } - self.calculate_rewards( - point_value, - vote_state, - stake_history, - inflation_point_calc_tracer, - fix_stake_deactivate, - ) - .map(|(stakers_reward, voters_reward, credits_observed)| { + stake.credits_observed = credits_observed; + stake.delegation.stake += stakers_reward; + (stakers_reward, voters_reward) + }) +} + +fn calculate_stake_points( + stake: &Stake, + vote_state: &VoteState, + stake_history: Option<&StakeHistory>, + inflation_point_calc_tracer: &mut Option, + fix_stake_deactivate: bool, +) -> u128 { + calculate_stake_points_and_credits( + stake, + vote_state, + stake_history, + inflation_point_calc_tracer, + fix_stake_deactivate, + ) + .0 +} + +/// for a given stake and vote_state, calculate how many +/// points were earned (credits * stake) and new value +/// for credits_observed were the points paid +fn calculate_stake_points_and_credits( + stake: &Stake, + new_vote_state: &VoteState, + stake_history: Option<&StakeHistory>, + inflation_point_calc_tracer: &mut Option, + fix_stake_deactivate: bool, +) -> (u128, u64) { + // if there is no newer credits since observed, return no point + if new_vote_state.credits() <= stake.credits_observed { + if fix_stake_deactivate { if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { - inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved( - self.credits_observed, - Some(credits_observed), - )); + inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into()); } - self.credits_observed = credits_observed; - self.delegation.stake += stakers_reward; - (stakers_reward, voters_reward) - }) - } - - pub fn calculate_points( - &self, - vote_state: &VoteState, - stake_history: Option<&StakeHistory>, - inflation_point_calc_tracer: &mut Option, - fix_stake_deactivate: bool, - ) -> u128 { - self.calculate_points_and_credits( - vote_state, - stake_history, - inflation_point_calc_tracer, - fix_stake_deactivate, - ) - .0 - } - - /// for a given stake and vote_state, calculate how many - /// points were earned (credits * stake) and new value - /// for credits_observed were the points paid - fn calculate_points_and_credits( - &self, - new_vote_state: &VoteState, - stake_history: Option<&StakeHistory>, - inflation_point_calc_tracer: &mut Option, - fix_stake_deactivate: bool, - ) -> (u128, u64) { - // if there is no newer credits since observed, return no point - if new_vote_state.credits() <= self.credits_observed { - if fix_stake_deactivate { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { - inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnCurrent.into()); - } - return (0, self.credits_observed); - } else { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { - inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnZero.into()); - } - return (0, 0); - } - } - - let mut points = 0; - let mut new_credits_observed = self.credits_observed; - - for (epoch, final_epoch_credits, initial_epoch_credits) in - new_vote_state.epoch_credits().iter().copied() - { - let stake = u128::from(self.delegation.stake( - epoch, - stake_history, - fix_stake_deactivate, - )); - - // figure out how much this stake has seen that - // for which the vote account has a record - let earned_credits = if self.credits_observed < initial_epoch_credits { - // the staker observed the entire epoch - final_epoch_credits - initial_epoch_credits - } else if self.credits_observed < final_epoch_credits { - // the staker registered sometime during the epoch, partial credit - final_epoch_credits - new_credits_observed - } else { - // the staker has already observed or been redeemed this epoch - // or was activated after this epoch - 0 - }; - let earned_credits = u128::from(earned_credits); - - // don't want to assume anything about order of the iterator... - new_credits_observed = new_credits_observed.max(final_epoch_credits); - - // finally calculate points for this epoch - let earned_points = stake * earned_credits; - points += earned_points; - - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { - inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints( - epoch, - stake, - earned_credits, - earned_points, - )); - } - } - - (points, new_credits_observed) - } - - /// for a given stake and vote_state, calculate what distributions and what updates should be made - /// returns a tuple in the case of a payout of: - /// * staker_rewards to be distributed - /// * voter_rewards to be distributed - /// * new value for credits_observed in the stake - /// returns None if there's no payout or if any deserved payout is < 1 lamport - pub fn calculate_rewards( - &self, - point_value: &PointValue, - vote_state: &VoteState, - stake_history: Option<&StakeHistory>, - inflation_point_calc_tracer: &mut Option, - fix_stake_deactivate: bool, - ) -> Option<(u64, u64, u64)> { - let (points, credits_observed) = self.calculate_points_and_credits( - vote_state, - stake_history, - inflation_point_calc_tracer, - fix_stake_deactivate, - ); - - // Drive credits_observed forward unconditionally when rewards are disabled - if point_value.rewards == 0 && fix_stake_deactivate { - return Some((0, 0, credits_observed)); - } - - if points == 0 { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { - inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into()); - } - return None; - } - if point_value.points == 0 { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { - inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into()); - } - return None; - } - - let rewards = points - .checked_mul(u128::from(point_value.rewards)) - .unwrap() - .checked_div(point_value.points) - .unwrap(); - - let rewards = u64::try_from(rewards).unwrap(); - - // don't bother trying to split if fractional lamports got truncated - if rewards == 0 { - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { - inflation_point_calc_tracer(&SkippedReason::ZeroReward.into()); - } - return None; - } - let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards); - if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { - inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards( - rewards, - voter_rewards, - staker_rewards, - (*point_value).clone(), - )); - } - - if (voter_rewards == 0 || staker_rewards == 0) && is_split { - // don't collect if we lose a whole lamport somewhere - // is_split means there should be tokens on both sides, - // uncool to move credits_observed if one side didn't get paid - return None; - } - - Some((staker_rewards, voter_rewards, credits_observed)) - } - - fn redelegate( - &mut self, - stake_lamports: u64, - voter_pubkey: &Pubkey, - vote_state: &VoteState, - clock: &Clock, - stake_history: &StakeHistory, - config: &Config, - can_reverse_deactivation: bool, - ) -> Result<(), StakeError> { - // If stake is currently active: - if self.stake(clock.epoch, Some(stake_history), true) != 0 { - // If pubkey of new voter is the same as current, - // and we are scheduled to start deactivating this epoch, - // we rescind deactivation - if self.delegation.voter_pubkey == *voter_pubkey - && clock.epoch == self.delegation.deactivation_epoch - && can_reverse_deactivation - { - self.delegation.deactivation_epoch = std::u64::MAX; - return Ok(()); - } else { - // can't redelegate to another pubkey if stake is active. - return Err(StakeError::TooSoonToRedelegate); - } - } - // Either the stake is freshly activated, is active but has been - // deactivated this epoch, or has fully de-activated. - // Redelegation implies either re-activation or un-deactivation - - self.delegation.stake = stake_lamports; - self.delegation.activation_epoch = clock.epoch; - self.delegation.deactivation_epoch = std::u64::MAX; - self.delegation.voter_pubkey = *voter_pubkey; - self.delegation.warmup_cooldown_rate = config.warmup_cooldown_rate; - self.credits_observed = vote_state.credits(); - Ok(()) - } - - fn split( - &mut self, - remaining_stake_delta: u64, - split_stake_amount: u64, - ) -> Result { - if remaining_stake_delta > self.delegation.stake { - return Err(StakeError::InsufficientStake); - } - self.delegation.stake -= remaining_stake_delta; - let new = Self { - delegation: Delegation { - stake: split_stake_amount, - ..self.delegation - }, - ..*self - }; - Ok(new) - } - - fn new( - stake: u64, - voter_pubkey: &Pubkey, - vote_state: &VoteState, - activation_epoch: Epoch, - config: &Config, - ) -> Self { - Self { - delegation: Delegation::new( - voter_pubkey, - stake, - activation_epoch, - config.warmup_cooldown_rate, - ), - credits_observed: vote_state.credits(), - } - } - - fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> { - if self.delegation.deactivation_epoch != std::u64::MAX { - Err(StakeError::AlreadyDeactivated) + return (0, stake.credits_observed); } else { - self.delegation.deactivation_epoch = epoch; - Ok(()) + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { + inflation_point_calc_tracer(&SkippedReason::ZeroCreditsAndReturnZero.into()); + } + return (0, 0); } } + + let mut points = 0; + let mut new_credits_observed = stake.credits_observed; + + for (epoch, final_epoch_credits, initial_epoch_credits) in + new_vote_state.epoch_credits().iter().copied() + { + let stake_amount = u128::from(stake.delegation.stake( + epoch, + stake_history, + fix_stake_deactivate, + )); + + // figure out how much this stake has seen that + // for which the vote account has a record + let earned_credits = if stake.credits_observed < initial_epoch_credits { + // the staker observed the entire epoch + final_epoch_credits - initial_epoch_credits + } else if stake.credits_observed < final_epoch_credits { + // the staker registered sometime during the epoch, partial credit + final_epoch_credits - new_credits_observed + } else { + // the staker has already observed or been redeemed this epoch + // or was activated after this epoch + 0 + }; + let earned_credits = u128::from(earned_credits); + + // don't want to assume anything about order of the iterator... + new_credits_observed = new_credits_observed.max(final_epoch_credits); + + // finally calculate points for this epoch + let earned_points = stake_amount * earned_credits; + points += earned_points; + + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { + inflation_point_calc_tracer(&InflationPointCalculationEvent::CalculatedPoints( + epoch, + stake_amount, + earned_credits, + earned_points, + )); + } + } + + (points, new_credits_observed) +} + +/// for a given stake and vote_state, calculate what distributions and what updates should be made +/// returns a tuple in the case of a payout of: +/// * staker_rewards to be distributed +/// * voter_rewards to be distributed +/// * new value for credits_observed in the stake +/// returns None if there's no payout or if any deserved payout is < 1 lamport +pub fn calculate_stake_rewards( + stake: &Stake, + point_value: &PointValue, + vote_state: &VoteState, + stake_history: Option<&StakeHistory>, + inflation_point_calc_tracer: &mut Option, + fix_stake_deactivate: bool, +) -> Option<(u64, u64, u64)> { + let (points, credits_observed) = calculate_stake_points_and_credits( + stake, + vote_state, + stake_history, + inflation_point_calc_tracer, + fix_stake_deactivate, + ); + + // Drive credits_observed forward unconditionally when rewards are disabled + if point_value.rewards == 0 && fix_stake_deactivate { + return Some((0, 0, credits_observed)); + } + + if points == 0 { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { + inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into()); + } + return None; + } + if point_value.points == 0 { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { + inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into()); + } + return None; + } + + let rewards = points + .checked_mul(u128::from(point_value.rewards)) + .unwrap() + .checked_div(point_value.points) + .unwrap(); + + let rewards = u64::try_from(rewards).unwrap(); + + // don't bother trying to split if fractional lamports got truncated + if rewards == 0 { + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { + inflation_point_calc_tracer(&SkippedReason::ZeroReward.into()); + } + return None; + } + let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(rewards); + if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { + inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards( + rewards, + voter_rewards, + staker_rewards, + (*point_value).clone(), + )); + } + + if (voter_rewards == 0 || staker_rewards == 0) && is_split { + // don't collect if we lose a whole lamport somewhere + // is_split means there should be tokens on both sides, + // uncool to move credits_observed if one side didn't get paid + return None; + } + + Some((staker_rewards, voter_rewards, credits_observed)) } pub trait StakeAccount { @@ -1035,7 +537,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { match self.state()? { StakeState::Initialized(meta) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; - let stake = Stake::new( + let stake = new_stake( self.lamports()?.saturating_sub(meta.rent_exempt_reserve), // can't stake the rent ;) vote_account.unsigned_key(), &State::::state(vote_account)?.convert_to_current(), @@ -1046,7 +548,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> { } StakeState::Stake(meta, mut stake) => { meta.authorized.check(signers, StakeAuthorize::Staker)?; - stake.redelegate( + redelegate( + &mut stake, self.lamports()?.saturating_sub(meta.rent_exempt_reserve), // can't stake the rent ;) vote_account.unsigned_key(), &State::::state(vote_account)?.convert_to_current(), @@ -1551,7 +1054,8 @@ pub fn redeem_rewards( )); } - if let Some((stakers_reward, voters_reward)) = stake.redeem_rewards( + if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards( + &mut stake, point_value, &vote_state, stake_history, @@ -1583,7 +1087,8 @@ pub fn calculate_points( let vote_state: VoteState = StateMut::::state(vote_account)?.convert_to_current(); - Ok(stake.calculate_points( + Ok(calculate_stake_points( + &stake, &vote_state, stake_history, &mut null_tracer(), @@ -1762,7 +1267,7 @@ fn do_create_account( rent_exempt_reserve, ..Meta::default() }, - Stake::new( + new_stake( lamports - rent_exempt_reserve, // underflow is an error, is basically: assert!(lamports > rent_exempt_reserve); voter_pubkey, &vote_state, @@ -1778,9 +1283,9 @@ fn do_create_account( #[cfg(test)] mod tests { use super::*; - use crate::id; use solana_sdk::{ account::{AccountSharedData, WritableAccount}, + clock::UnixTimestamp, native_token, process_instruction::MockInvokeContext, pubkey::Pubkey, @@ -1789,15 +1294,6 @@ mod tests { use solana_vote_program::vote_state; use std::{cell::RefCell, iter::FromIterator}; - impl Meta { - pub fn auto(authorized: &Pubkey) -> Self { - Self { - authorized: Authorized::auto(authorized), - ..Meta::default() - } - } - } - #[test] fn test_authorized_authorize() { let staker = solana_sdk::pubkey::new_rand(); @@ -1944,7 +1440,7 @@ mod tests { .set_state(&StakeState::default()) .expect("set_state"); - assert_eq!(StakeState::stake_from(&stake_account), None); + assert_eq!(stake_from(&stake_account), None); } #[test] @@ -2051,7 +1547,7 @@ mod tests { .is_ok()); // verify that delegate() looks right, compare against hand-rolled - let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap(); + let stake = stake_from(&stake_keyed_account.account.borrow()).unwrap(); assert_eq!( stake, Stake { @@ -2112,7 +1608,7 @@ mod tests { .unwrap(); // verify that deactivation has been cleared - let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap(); + let stake = stake_from(&stake_keyed_account.account.borrow()).unwrap(); assert_eq!(stake.delegation.deactivation_epoch, std::u64::MAX); // verify that delegate to a different vote account fails @@ -2167,7 +1663,7 @@ mod tests { ); // verify that delegate() looks right, compare against hand-rolled - let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap(); + let stake = stake_from(&stake_keyed_account.account.borrow()).unwrap(); assert_eq!( stake, Stake { @@ -2858,7 +2354,7 @@ mod tests { ); // check that we see what we expect assert_eq!( - StakeState::from(&stake_keyed_account.account.borrow()).unwrap(), + from(&stake_keyed_account.account.borrow()).unwrap(), StakeState::Initialized(Meta { lockup: Lockup { unix_timestamp: 0, @@ -3147,7 +2643,7 @@ mod tests { ); if let StakeState::Initialized(Meta { lockup, .. }) = - StakeState::from(&stake_keyed_account.account.borrow()).unwrap() + from(&stake_keyed_account.account.borrow()).unwrap() { assert_eq!(lockup.unix_timestamp, 2); assert_eq!(lockup.epoch, 1); @@ -3170,7 +2666,7 @@ mod tests { ); if let StakeState::Initialized(Meta { lockup, .. }) = - StakeState::from(&stake_keyed_account.account.borrow()).unwrap() + from(&stake_keyed_account.account.borrow()).unwrap() { assert_eq!(lockup.unix_timestamp, 2); assert_eq!(lockup.epoch, 3); @@ -3194,7 +2690,7 @@ mod tests { ); if let StakeState::Initialized(Meta { lockup, .. }) = - StakeState::from(&stake_keyed_account.account.borrow()).unwrap() + from(&stake_keyed_account.account.borrow()).unwrap() { assert_eq!(lockup.unix_timestamp, 2); assert_eq!(lockup.epoch, 3); @@ -3594,11 +3090,9 @@ mod tests { let stake_history = create_stake_history_from_delegations( None, 0..future.epoch, - &[ - StakeState::stake_from(&stake_keyed_account.account.borrow()) - .unwrap() - .delegation, - ], + &[stake_from(&stake_keyed_account.account.borrow()) + .unwrap() + .delegation], ); // Try to withdraw stake @@ -3893,7 +3387,7 @@ mod tests { // assume stake.stake() is right // bootstrap means fully-vested stake at epoch 0 let stake_lamports = 1; - let mut stake = Stake::new( + let mut stake = new_stake( stake_lamports, &Pubkey::default(), &vote_state, @@ -3904,7 +3398,8 @@ mod tests { // this one can't collect now, credits_observed == vote_state.credits() assert_eq!( None, - stake.redeem_rewards( + redeem_stake_rewards( + &mut stake, &PointValue { rewards: 1_000_000_000, points: 1 @@ -3923,7 +3418,8 @@ mod tests { // this one should be able to collect exactly 2 assert_eq!( Some((stake_lamports * 2, 0)), - stake.redeem_rewards( + redeem_stake_rewards( + &mut stake, &PointValue { rewards: 1, points: 1 @@ -3948,7 +3444,7 @@ mod tests { // bootstrap means fully-vested stake at epoch 0 with // 10_000_000 SOL is a big but not unreasaonable stake - let stake = Stake::new( + let stake = new_stake( native_token::sol_to_lamports(10_000_000f64), &Pubkey::default(), &vote_state, @@ -3959,7 +3455,8 @@ mod tests { // this one can't collect now, credits_observed == vote_state.credits() assert_eq!( None, - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 1_000_000_000, points: 1 @@ -3981,7 +3478,7 @@ mod tests { // no overflow on points assert_eq!( u128::from(stake.delegation.stake) * epoch_slots, - stake.calculate_points(&vote_state, None, &mut null_tracer(), true) + calculate_stake_points(&stake, &vote_state, None, &mut null_tracer(), true) ); } @@ -3990,7 +3487,7 @@ mod tests { let mut vote_state = VoteState::default(); // assume stake.stake() is right // bootstrap means fully-vested stake at epoch 0 - let mut stake = Stake::new( + let mut stake = new_stake( 1, &Pubkey::default(), &vote_state, @@ -4001,7 +3498,8 @@ mod tests { // this one can't collect now, credits_observed == vote_state.credits() assert_eq!( None, - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 1_000_000_000, points: 1 @@ -4020,7 +3518,8 @@ mod tests { // this one should be able to collect exactly 2 assert_eq!( Some((stake.delegation.stake * 2, 0, 2)), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 2, points: 2 // all his @@ -4036,7 +3535,8 @@ mod tests { // this one should be able to collect exactly 1 (already observed one) assert_eq!( Some((stake.delegation.stake, 0, 2)), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 1, points: 1 @@ -4055,7 +3555,8 @@ mod tests { // this one should be able to collect the one just added assert_eq!( Some((stake.delegation.stake, 0, 3)), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 2, points: 2 @@ -4072,7 +3573,8 @@ mod tests { // this one should be able to collect 2 now assert_eq!( Some((stake.delegation.stake * 2, 0, 4)), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 2, points: 2 @@ -4095,7 +3597,8 @@ mod tests { 0, 4 )), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 4, points: 4 @@ -4112,7 +3615,8 @@ mod tests { vote_state.commission = 1; assert_eq!( None, // would be Some((0, 2 * 1 + 1 * 2, 4)), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 4, points: 4 @@ -4126,7 +3630,8 @@ mod tests { vote_state.commission = 99; assert_eq!( None, // would be Some((0, 2 * 1 + 1 * 2, 4)), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 4, points: 4 @@ -4143,7 +3648,8 @@ mod tests { // paying rewards when inflation is turned on. assert_eq!( Some((0, 0, 4)), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 0, points: 4 @@ -4160,7 +3666,8 @@ mod tests { stake.credits_observed = 4; assert_eq!( Some((0, 0, 4)), - stake.calculate_rewards( + calculate_stake_rewards( + &stake, &PointValue { rewards: 0, points: 4 @@ -4175,11 +3682,17 @@ mod tests { // assert the previous behavior is preserved where fix_stake_deactivate=false assert_eq!( (0, 0), - stake.calculate_points_and_credits(&vote_state, None, &mut null_tracer(), false) + calculate_stake_points_and_credits( + &stake, + &vote_state, + None, + &mut null_tracer(), + false + ) ); assert_eq!( (0, 4), - stake.calculate_points_and_credits(&vote_state, None, &mut null_tracer(), true) + calculate_stake_points_and_credits(&stake, &vote_state, None, &mut null_tracer(), true) ); } @@ -4254,7 +3767,7 @@ mod tests { Ok(()) ); if let StakeState::Initialized(Meta { authorized, .. }) = - StakeState::from(&stake_keyed_account.account.borrow()).unwrap() + from(&stake_keyed_account.account.borrow()).unwrap() { assert_eq!(authorized.staker, stake_pubkey0); assert_eq!(authorized.withdrawer, stake_pubkey0); @@ -4292,7 +3805,7 @@ mod tests { Ok(()) ); if let StakeState::Initialized(Meta { authorized, .. }) = - StakeState::from(&stake_keyed_account.account.borrow()).unwrap() + from(&stake_keyed_account.account.borrow()).unwrap() { assert_eq!(authorized.staker, stake_pubkey2); } @@ -4309,7 +3822,7 @@ mod tests { Ok(()) ); if let StakeState::Initialized(Meta { authorized, .. }) = - StakeState::from(&stake_keyed_account.account.borrow()).unwrap() + from(&stake_keyed_account.account.borrow()).unwrap() { assert_eq!(authorized.staker, stake_pubkey2); } @@ -4584,7 +4097,7 @@ mod tests { let stake_lamports = 42; let stake_account = AccountSharedData::new_ref_data_with_space( stake_lamports, - &StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)), + &StakeState::Stake(Meta::auto(&stake_pubkey), just_stake(stake_lamports)), std::mem::size_of::(), &id(), ) @@ -4608,15 +4121,13 @@ mod tests { Err(InstructionError::InvalidAccountData) ); } - impl Stake { - fn just_stake(stake: u64) -> Self { - Self { - delegation: Delegation { - stake, - ..Delegation::default() - }, - ..Stake::default() - } + fn just_stake(stake: u64) -> Stake { + Stake { + delegation: Delegation { + stake, + ..Delegation::default() + }, + ..Stake::default() } } @@ -4628,7 +4139,7 @@ mod tests { stake_lamports, &StakeState::Stake( Meta::auto(&stake_pubkey), - Stake::just_stake(stake_lamports / 2 - 1), + just_stake(stake_lamports / 2 - 1), ), std::mem::size_of::(), &id(), @@ -4671,10 +4182,7 @@ mod tests { // test splitting both an Initialized stake and a Staked stake for state in &[ StakeState::Initialized(meta), - StakeState::Stake( - meta, - Stake::just_stake(stake_lamports - rent_exempt_reserve), - ), + StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)), ] { let stake_account = AccountSharedData::new_ref_data_with_space( stake_lamports, @@ -4769,7 +4277,7 @@ mod tests { // test splitting both an Initialized stake and a Staked stake for state in &[ StakeState::Initialized(Meta::auto(&stake_pubkey)), - StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)), + StakeState::Stake(Meta::auto(&stake_pubkey), just_stake(stake_lamports)), ] { let split_stake_account = AccountSharedData::new_ref_data_with_space( 0, @@ -4874,7 +4382,7 @@ mod tests { let stake_account = AccountSharedData::new_ref_data_with_space( stake_lamports, - &StakeState::Stake(Meta::auto(&stake_pubkey), Stake::just_stake(stake_lamports)), + &StakeState::Stake(Meta::auto(&stake_pubkey), just_stake(stake_lamports)), std::mem::size_of::(), &id(), ) @@ -4903,10 +4411,7 @@ mod tests { ..Meta::default() }; - let state = StakeState::Stake( - meta, - Stake::just_stake(stake_lamports - rent_exempt_reserve), - ); + let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)); // Test various account prefunding, including empty, less than rent_exempt_reserve, exactly // rent_exempt_reserve, and more than rent_exempt_reserve. The empty case is not covered in // test_split, since that test uses a Meta with rent_exempt_reserve = 0 @@ -5006,10 +4511,7 @@ mod tests { ..Meta::default() }; - let state = StakeState::Stake( - meta, - Stake::just_stake(stake_lamports - rent_exempt_reserve), - ); + let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)); let expected_rent_exempt_reserve = calculate_split_rent_exempt_reserve( meta.rent_exempt_reserve, @@ -5133,10 +4635,7 @@ mod tests { let stake_lamports = expected_rent_exempt_reserve + 1; let split_amount = stake_lamports - (rent_exempt_reserve + 1); // Enough so that split stake is > 0 - let state = StakeState::Stake( - meta, - Stake::just_stake(stake_lamports - rent_exempt_reserve), - ); + let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)); let split_lamport_balances = vec![ 0, @@ -5196,10 +4695,7 @@ mod tests { // test splitting both an Initialized stake and a Staked stake for state in &[ StakeState::Initialized(meta), - StakeState::Stake( - meta, - Stake::just_stake(stake_lamports - rent_exempt_reserve), - ), + StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)), ] { let split_stake_account = AccountSharedData::new_ref_data_with_space( 0, @@ -5282,10 +4778,7 @@ mod tests { ..Meta::default() }; - let state = StakeState::Stake( - meta, - Stake::just_stake(stake_lamports - rent_exempt_reserve), - ); + let state = StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)); // Test various account prefunding, including empty, less than rent_exempt_reserve, exactly // rent_exempt_reserve, and more than rent_exempt_reserve. Technically, the empty case is // covered in test_split_100_percent_of_source, but included here as well for readability @@ -5361,10 +4854,7 @@ mod tests { for state in &[ StakeState::Initialized(meta), - StakeState::Stake( - meta, - Stake::just_stake(stake_lamports - rent_exempt_reserve), - ), + StakeState::Stake(meta, just_stake(stake_lamports - rent_exempt_reserve)), ] { // Test that splitting to a larger account fails let split_stake_account = AccountSharedData::new_ref_data_with_space( @@ -5484,17 +4974,11 @@ mod tests { for state in &[ StakeState::Initialized(Meta::auto(&authorized_pubkey)), - StakeState::Stake( - Meta::auto(&authorized_pubkey), - Stake::just_stake(stake_lamports), - ), + StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), ] { for source_state in &[ StakeState::Initialized(Meta::auto(&authorized_pubkey)), - StakeState::Stake( - Meta::auto(&authorized_pubkey), - Stake::just_stake(stake_lamports), - ), + StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), ] { let stake_account = AccountSharedData::new_ref_data_with_space( stake_lamports, @@ -5647,16 +5131,13 @@ mod tests { for state in &[ StakeState::Initialized(Meta::auto(&authorized_pubkey)), - StakeState::Stake( - Meta::auto(&authorized_pubkey), - Stake::just_stake(stake_lamports), - ), + StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), ] { for source_state in &[ StakeState::Initialized(Meta::auto(&wrong_authorized_pubkey)), StakeState::Stake( Meta::auto(&wrong_authorized_pubkey), - Stake::just_stake(stake_lamports), + just_stake(stake_lamports), ), ] { let stake_account = AccountSharedData::new_ref_data_with_space( @@ -5718,10 +5199,7 @@ mod tests { StakeState::Uninitialized, StakeState::RewardsPool, StakeState::Initialized(Meta::auto(&authorized_pubkey)), - StakeState::Stake( - Meta::auto(&authorized_pubkey), - Stake::just_stake(stake_lamports), - ), + StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), ] { for source_state in &[StakeState::Uninitialized, StakeState::RewardsPool] { let stake_account = AccountSharedData::new_ref_data_with_space( @@ -5769,10 +5247,7 @@ mod tests { let stake_account = AccountSharedData::new_ref_data_with_space( stake_lamports, - &StakeState::Stake( - Meta::auto(&authorized_pubkey), - Stake::just_stake(stake_lamports), - ), + &StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), std::mem::size_of::(), &id(), ) @@ -5781,10 +5256,7 @@ mod tests { let source_stake_account = AccountSharedData::new_ref_data_with_space( stake_lamports, - &StakeState::Stake( - Meta::auto(&authorized_pubkey), - Stake::just_stake(stake_lamports), - ), + &StakeState::Stake(Meta::auto(&authorized_pubkey), just_stake(stake_lamports)), std::mem::size_of::(), &solana_sdk::pubkey::new_rand(), ) @@ -6190,8 +5662,7 @@ mod tests { ), Ok(()) ); - let authorized = - StakeState::authorized_from(&stake_keyed_account.try_account_ref().unwrap()).unwrap(); + let authorized = authorized_from(&stake_keyed_account.try_account_ref().unwrap()).unwrap(); assert_eq!(authorized.staker, new_staker_pubkey); let other_pubkey = solana_sdk::pubkey::new_rand(); @@ -6237,8 +5708,7 @@ mod tests { ), Ok(()) ); - let stake = - StakeState::stake_from(&stake_keyed_account.try_account_ref().unwrap()).unwrap(); + let stake = stake_from(&stake_keyed_account.try_account_ref().unwrap()).unwrap(); assert_eq!(stake.delegation.voter_pubkey, new_voter_pubkey); // Test another staking action @@ -6324,7 +5794,7 @@ mod tests { true, ) .unwrap(); - let stake = StakeState::stake_from(&stake_account.borrow()).unwrap(); + let stake = stake_from(&stake_account.borrow()).unwrap(); assert_eq!( stake.delegation.stake, stake_keyed_account.lamports().unwrap() - rent_exempt_reserve, @@ -6351,7 +5821,7 @@ mod tests { true, ) .unwrap(); - let stake = StakeState::stake_from(&stake_account.borrow()).unwrap(); + let stake = stake_from(&stake_account.borrow()).unwrap(); assert_eq!( stake.delegation.stake, stake_keyed_account.lamports().unwrap() - rent_exempt_reserve, diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index dfbd15423f..2d3aa4fbac 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -38,7 +38,6 @@ solana-perf = { path = "../perf", version = "=1.8.0" } solana-poh = { path = "../poh", version = "=1.8.0" } solana-runtime = { path = "../runtime", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.8.0" } solana-transaction-status = { path = "../transaction-status", version = "=1.8.0" } solana-version = { path = "../version", version = "=1.8.0" } @@ -52,6 +51,7 @@ tokio-util = { version = "0.3", features = ["codec"] } # This crate needs to sta serial_test = "0.4.0" solana-logger = { path = "../logger", version = "=1.8.0" } solana-net-utils = { path = "../net-utils", version = "=1.8.0" } +solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } symlink = "0.1.0" [lib] diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 6aea3fe50c..ac0e3a70f2 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -62,12 +62,12 @@ use { pubkey::Pubkey, sanitize::Sanitize, signature::{Keypair, Signature, Signer}, + stake::state::StakeState, stake_history::StakeHistory, system_instruction, sysvar::stake_history, transaction::{self, Transaction}, }, - solana_stake_program::stake_state::StakeState, solana_transaction_status::{ EncodedConfirmedTransaction, Reward, RewardType, TransactionConfirmationStatus, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding, diff --git a/rpc/src/rpc_pubsub.rs b/rpc/src/rpc_pubsub.rs index 7ab789a87d..1e897835f3 100644 --- a/rpc/src/rpc_pubsub.rs +++ b/rpc/src/rpc_pubsub.rs @@ -569,13 +569,14 @@ mod tests { message::Message, pubkey::Pubkey, signature::{Keypair, Signer}, + stake::{ + self, instruction as stake_instruction, + state::{Authorized, Lockup, StakeAuthorize}, + }, system_instruction, system_program, system_transaction, transaction::{self, Transaction}, }, - solana_stake_program::{ - self, stake_instruction, - stake_state::{Authorized, Lockup, StakeAuthorize, StakeState}, - }, + solana_stake_program::stake_state, solana_vote_program::vote_state::Vote, std::{ sync::{atomic::AtomicBool, RwLock}, @@ -791,7 +792,7 @@ mod tests { let stake_authority = Keypair::new(); let from = Keypair::new(); let stake_account = Keypair::new(); - let stake_program_id = solana_stake_program::id(); + let stake_program_id = stake::program::id(); let bank = Bank::new(&genesis_config); let blockhash = bank.last_blockhash(); let bank_forks = Arc::new(RwLock::new(BankForks::new(bank))); @@ -887,7 +888,7 @@ mod tests { let bank = bank_forks.read().unwrap()[1].clone(); let account = bank.get_account(&stake_account.pubkey()).unwrap(); assert_eq!( - StakeState::authorized_from(&account).unwrap().staker, + stake_state::authorized_from(&account).unwrap().staker, new_stake_authority ); } diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index d2aedd5ead..6ee7146c8e 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -1343,7 +1343,7 @@ pub(crate) mod tests { solana_sdk::{ message::Message, signature::{Keypair, Signer}, - system_instruction, system_program, system_transaction, + stake, system_instruction, system_program, system_transaction, transaction::Transaction, }, std::{fmt::Debug, sync::mpsc::channel}, @@ -1544,7 +1544,7 @@ pub(crate) mod tests { blockhash, 1, 16, - &solana_stake_program::id(), + &stake::program::id(), ); bank_forks .write() @@ -1567,7 +1567,7 @@ pub(crate) mod tests { optimistically_confirmed_bank, ); subscriptions.add_program_subscription( - solana_stake_program::id(), + stake::program::id(), Some(RpcProgramAccountsConfig { account_config: RpcAccountInfoConfig { commitment: Some(CommitmentConfig::processed()), @@ -1584,7 +1584,7 @@ pub(crate) mod tests { .program_subscriptions .read() .unwrap() - .contains_key(&solana_stake_program::id())); + .contains_key(&stake::program::id())); subscriptions.notify_subscribers(CommitmentSlots::default()); let (response, _) = robust_poll_or_panic(transport_receiver); @@ -1616,7 +1616,7 @@ pub(crate) mod tests { .program_subscriptions .read() .unwrap() - .contains_key(&solana_stake_program::id())); + .contains_key(&stake::program::id())); } #[test] @@ -2044,7 +2044,7 @@ pub(crate) mod tests { blockhash, 1, 16, - &solana_stake_program::id(), + &stake::program::id(), ); // Add the transaction to the 1st bank and then freeze the bank diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 357eba10b8..0e5f05c62f 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -96,6 +96,7 @@ use solana_sdk::{ signature::{Keypair, Signature}, slot_hashes::SlotHashes, slot_history::SlotHistory, + stake::{self, state::Delegation}, stake_weighted_timestamp::{ calculate_stake_weighted_timestamp, MaxAllowableDrift, MAX_ALLOWABLE_DRIFT_PERCENTAGE, MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST, MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW, @@ -105,9 +106,7 @@ use solana_sdk::{ timing::years_as_slots, transaction::{self, Result, Transaction, TransactionError}, }; -use solana_stake_program::stake_state::{ - self, Delegation, InflationPointCalculationEvent, PointValue, -}; +use solana_stake_program::stake_state::{self, InflationPointCalculationEvent, PointValue}; use solana_vote_program::vote_instruction::VoteInstruction; use std::{ borrow::Cow, @@ -1918,7 +1917,7 @@ impl Bank { if self .feature_set .is_active(&feature_set::filter_stake_delegation_accounts::id()) - && (stake_account.owner() != &solana_stake_program::id() + && (stake_account.owner() != &stake::program::id() || vote_account.owner() != &solana_vote_program::id()) { datapoint_warn!( @@ -5357,15 +5356,15 @@ pub(crate) mod tests { process_instruction::InvokeContext, rent::Rent, signature::{keypair_from_seed, Keypair, Signer}, + stake::{ + instruction as stake_instruction, + state::{Authorized, Delegation, Lockup, Stake}, + }, system_instruction::{self, SystemError}, system_program, sysvar::{fees::Fees, rewards::Rewards}, timing::duration_as_s, }; - use solana_stake_program::{ - stake_instruction, - stake_state::{self, Authorized, Delegation, Lockup, Stake}, - }; use solana_vote_program::{ vote_instruction, vote_state::{ @@ -9558,7 +9557,7 @@ pub(crate) mod tests { let pubkey = solana_sdk::pubkey::new_rand(); genesis_config.add_account( pubkey, - solana_stake_program::stake_state::create_lockup_stake_account( + stake_state::create_lockup_stake_account( &Authorized::auto(&pubkey), &Lockup::default(), &Rent::default(), diff --git a/runtime/src/builtins.rs b/runtime/src/builtins.rs index 899ad9a04e..a160b590ef 100644 --- a/runtime/src/builtins.rs +++ b/runtime/src/builtins.rs @@ -7,7 +7,7 @@ use solana_sdk::{ instruction::InstructionError, process_instruction::{stable_log, InvokeContext, ProcessInstructionWithContext}, pubkey::Pubkey, - system_program, + stake, system_program, }; fn process_instruction_with_program_logging( @@ -56,7 +56,7 @@ fn genesis_builtins() -> Vec { ), Builtin::new( "stake_program", - solana_stake_program::id(), + stake::program::id(), with_program_logging!(solana_stake_program::stake_instruction::process_instruction), ), Builtin::new( diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index 25dec5e47b..29457764cf 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -8,10 +8,10 @@ use solana_sdk::{ pubkey::Pubkey, rent::Rent, signature::{Keypair, Signer}, + stake::state::StakeState, system_program, }; use solana_stake_program::stake_state; -use solana_stake_program::stake_state::StakeState; use solana_vote_program::vote_state; use std::borrow::Borrow; diff --git a/runtime/src/non_circulating_supply.rs b/runtime/src/non_circulating_supply.rs index 76668ac811..e4175066bc 100644 --- a/runtime/src/non_circulating_supply.rs +++ b/runtime/src/non_circulating_supply.rs @@ -4,8 +4,12 @@ use { bank::Bank, }, log::*, - solana_sdk::{account::ReadableAccount, pubkey::Pubkey}, - solana_stake_program::stake_state::StakeState, + solana_sdk::{ + account::ReadableAccount, + pubkey::Pubkey, + stake::{self, state::StakeState}, + }, + solana_stake_program::stake_state, std::{collections::HashSet, sync::Arc}, }; @@ -32,19 +36,19 @@ pub fn calculate_non_circulating_supply(bank: &Arc) -> ScanResult { if meta.lockup.is_in_force(&clock, None) @@ -196,8 +200,8 @@ mod tests { account::AccountSharedData, epoch_schedule::EpochSchedule, genesis_config::{ClusterType, GenesisConfig}, + stake::state::{Authorized, Lockup, Meta}, }; - use solana_stake_program::stake_state::{Authorized, Lockup, Meta, StakeState}; use std::{collections::BTreeMap, sync::Arc}; fn new_from_parent(parent: &Arc) -> Bank { @@ -236,7 +240,7 @@ mod tests { balance, &StakeState::Initialized(meta), std::mem::size_of::(), - &solana_stake_program::id(), + &stake::program::id(), ) .unwrap(); accounts.insert(pubkey, stake_account); diff --git a/runtime/src/serde_snapshot/tests.rs b/runtime/src/serde_snapshot/tests.rs index 40f727eadd..6d8436f313 100644 --- a/runtime/src/serde_snapshot/tests.rs +++ b/runtime/src/serde_snapshot/tests.rs @@ -293,7 +293,7 @@ mod test_bank_serialize { // This some what long test harness is required to freeze the ABI of // Bank's serialization due to versioned nature - #[frozen_abi(digest = "DuRGntVwLGNAv5KooafUSpxk67BPAx2yC7Z8A9c8wr2G")] + #[frozen_abi(digest = "6msodEzE7YzFtorBhiP6ax4PKBaPZTkmYdGAdpoxLCvV")] #[derive(Serialize, AbiExample)] pub struct BankAbiTestWrapperFuture { #[serde(serialize_with = "wrapper_future")] diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 69dc59053a..7148094b31 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -5,9 +5,13 @@ use solana_sdk::{ account::{AccountSharedData, ReadableAccount}, clock::Epoch, pubkey::Pubkey, + stake::{ + self, + state::{Delegation, StakeState}, + }, sysvar::stake_history::StakeHistory, }; -use solana_stake_program::stake_state::{new_stake_history_entry, Delegation, StakeState}; +use solana_stake_program::stake_state; use solana_vote_program::vote_state::VoteState; use std::{borrow::Borrow, collections::HashMap}; @@ -42,7 +46,7 @@ impl Stakes { let mut stake_history_upto_prev_epoch = self.stake_history.clone(); stake_history_upto_prev_epoch.add( prev_epoch, - new_stake_history_entry( + stake_state::new_stake_history_entry( prev_epoch, self.stake_delegations .iter() @@ -111,7 +115,7 @@ impl Stakes { pub fn is_stake(account: &AccountSharedData) -> bool { solana_vote_program::check_id(account.owner()) - || solana_stake_program::check_id(account.owner()) + || stake::program::check_id(account.owner()) && account.data().len() >= std::mem::size_of::() } @@ -148,7 +152,7 @@ impl Stakes { .insert(*pubkey, (stake, ArcVoteAccount::from(account.clone()))); } old.map(|(_, account)| account) - } else if solana_stake_program::check_id(account.owner()) { + } else if stake::program::check_id(account.owner()) { // old_stake is stake lamports and voter_pubkey from the pre-store() version let old_stake = self.stake_delegations.get(pubkey).map(|delegation| { ( @@ -157,7 +161,7 @@ impl Stakes { ) }); - let delegation = StakeState::delegation_from(account); + let delegation = stake_state::delegation_from(account); let stake = delegation.map(|delegation| { ( @@ -308,7 +312,7 @@ pub mod tests { stakes.store(&vote_pubkey, &vote_account, true, true); stakes.store(&stake_pubkey, &stake_account, true, true); - let stake = StakeState::stake_from(&stake_account).unwrap(); + let stake = stake_state::stake_from(&stake_account).unwrap(); { let vote_accounts = stakes.vote_accounts(); assert!(vote_accounts.get(&vote_pubkey).is_some()); @@ -332,7 +336,7 @@ pub mod tests { // activate more let (_stake_pubkey, mut stake_account) = create_stake_account(42, &vote_pubkey); stakes.store(&stake_pubkey, &stake_account, true, true); - let stake = StakeState::stake_from(&stake_account).unwrap(); + let stake = stake_state::stake_from(&stake_account).unwrap(); { let vote_accounts = stakes.vote_accounts(); assert!(vote_accounts.get(&vote_pubkey).is_some()); @@ -463,7 +467,7 @@ pub mod tests { // delegates to vote_pubkey stakes.store(&stake_pubkey, &stake_account, true, true); - let stake = StakeState::stake_from(&stake_account).unwrap(); + let stake = stake_state::stake_from(&stake_account).unwrap(); { let vote_accounts = stakes.vote_accounts(); @@ -523,7 +527,7 @@ pub mod tests { stakes.store(&vote_pubkey, &vote_account, true, true); stakes.store(&stake_pubkey, &stake_account, true, true); - let stake = StakeState::stake_from(&stake_account).unwrap(); + let stake = stake_state::stake_from(&stake_account).unwrap(); { let vote_accounts = stakes.vote_accounts(); @@ -564,7 +568,7 @@ pub mod tests { // not a stake account, and whacks above entry stakes.store( &stake_pubkey, - &AccountSharedData::new(1, 0, &solana_stake_program::id()), + &AccountSharedData::new(1, 0, &stake::program::id()), true, true, ); diff --git a/runtime/tests/stake.rs b/runtime/tests/stake.rs index 5f1d36bd1d..7c3d812641 100644 --- a/runtime/tests/stake.rs +++ b/runtime/tests/stake.rs @@ -11,12 +11,13 @@ use solana_sdk::{ message::Message, pubkey::Pubkey, signature::{Keypair, Signer}, + stake::{ + self, instruction as stake_instruction, + state::{Authorized, Lockup, StakeState}, + }, sysvar::{self, stake_history::StakeHistory}, }; -use solana_stake_program::{ - stake_instruction::{self}, - stake_state::{self, StakeState}, -}; +use solana_stake_program::stake_state; use solana_vote_program::{ vote_instruction, vote_state::{Vote, VoteInit, VoteState, VoteStateVersions}, @@ -69,7 +70,7 @@ fn fill_epoch_with_votes( } fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool { - let stake = StakeState::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap(); + let stake = stake_state::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap(); stake.delegation.stake == stake.stake( @@ -85,7 +86,7 @@ fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool { } fn get_staked(bank: &Bank, stake_pubkey: &Pubkey) -> u64 { - StakeState::stake_from(&bank.get_account(stake_pubkey).unwrap()) + stake_state::stake_from(&bank.get_account(stake_pubkey).unwrap()) .unwrap() .stake( bank.epoch(), @@ -118,9 +119,9 @@ fn test_stake_create_and_split_single_signature() { let bank_client = BankClient::new_shared(&Arc::new(Bank::new(&genesis_config))); let stake_address = - Pubkey::create_with_seed(&staker_pubkey, "stake", &solana_stake_program::id()).unwrap(); + Pubkey::create_with_seed(&staker_pubkey, "stake", &stake::program::id()).unwrap(); - let authorized = stake_state::Authorized::auto(&staker_pubkey); + let authorized = Authorized::auto(&staker_pubkey); let lamports = 1_000_000; @@ -132,7 +133,7 @@ fn test_stake_create_and_split_single_signature() { &staker_pubkey, // base "stake", // seed &authorized, - &stake_state::Lockup::default(), + &Lockup::default(), lamports, ), Some(&staker_pubkey), @@ -145,8 +146,7 @@ fn test_stake_create_and_split_single_signature() { // split the stake let split_stake_address = - Pubkey::create_with_seed(&staker_pubkey, "split_stake", &solana_stake_program::id()) - .unwrap(); + Pubkey::create_with_seed(&staker_pubkey, "split_stake", &stake::program::id()).unwrap(); // Test split let message = Message::new( &stake_instruction::split_with_seed( @@ -189,9 +189,9 @@ fn test_stake_create_and_split_to_existing_system_account() { let bank_client = BankClient::new_shared(&Arc::new(Bank::new(&genesis_config))); let stake_address = - Pubkey::create_with_seed(&staker_pubkey, "stake", &solana_stake_program::id()).unwrap(); + Pubkey::create_with_seed(&staker_pubkey, "stake", &stake::program::id()).unwrap(); - let authorized = stake_state::Authorized::auto(&staker_pubkey); + let authorized = Authorized::auto(&staker_pubkey); let lamports = 1_000_000; @@ -203,7 +203,7 @@ fn test_stake_create_and_split_to_existing_system_account() { &staker_pubkey, // base "stake", // seed &authorized, - &stake_state::Lockup::default(), + &Lockup::default(), lamports, ), Some(&staker_pubkey), @@ -214,8 +214,7 @@ fn test_stake_create_and_split_to_existing_system_account() { .expect("failed to create and delegate stake account"); let split_stake_address = - Pubkey::create_with_seed(&staker_pubkey, "split_stake", &solana_stake_program::id()) - .unwrap(); + 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; @@ -290,7 +289,7 @@ fn test_stake_account_lifetime() { .send_and_confirm_message(&[&mint_keypair, &vote_keypair, &identity_keypair], message) .expect("failed to create vote account"); - let authorized = stake_state::Authorized::auto(&stake_pubkey); + let authorized = Authorized::auto(&stake_pubkey); // Create stake account and delegate to vote account let message = Message::new( &stake_instruction::create_account_and_delegate_stake( @@ -298,7 +297,7 @@ fn test_stake_account_lifetime() { &stake_pubkey, &vote_pubkey, &authorized, - &stake_state::Lockup::default(), + &Lockup::default(), 1_000_000, ), Some(&mint_pubkey), @@ -516,8 +515,7 @@ fn test_create_stake_account_from_seed() { let bank_client = BankClient::new_shared(&bank); let seed = "test-string"; - let stake_pubkey = - Pubkey::create_with_seed(&mint_pubkey, seed, &solana_stake_program::id()).unwrap(); + let stake_pubkey = Pubkey::create_with_seed(&mint_pubkey, seed, &stake::program::id()).unwrap(); // Create Vote Account let message = Message::new( @@ -538,7 +536,7 @@ fn test_create_stake_account_from_seed() { .send_and_confirm_message(&[&mint_keypair, &vote_keypair, &identity_keypair], message) .expect("failed to create vote account"); - let authorized = stake_state::Authorized::auto(&mint_pubkey); + let authorized = Authorized::auto(&mint_pubkey); // Create stake account and delegate to vote account let message = Message::new( &stake_instruction::create_account_with_seed_and_delegate_stake( @@ -548,7 +546,7 @@ fn test_create_stake_account_from_seed() { seed, &vote_pubkey, &authorized, - &stake_state::Lockup::default(), + &Lockup::default(), 1_000_000, ), Some(&mint_pubkey), diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 8bc14cf953..bed762359d 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -43,6 +43,7 @@ pub mod serialize_utils; pub mod short_vec; pub mod slot_hashes; pub mod slot_history; +pub mod stake; pub mod stake_history; pub mod system_instruction; pub mod system_program; diff --git a/sdk/program/src/stake/config.rs b/sdk/program/src/stake/config.rs new file mode 100644 index 0000000000..9470b2940a --- /dev/null +++ b/sdk/program/src/stake/config.rs @@ -0,0 +1,28 @@ +//! config for staking +//! carries variables that the stake program cares about +use serde_derive::{Deserialize, Serialize}; + +// stake config ID +crate::declare_id!("StakeConfig11111111111111111111111111111111"); + +// means that no more than RATE of current effective stake may be added or subtracted per +// epoch +pub const DEFAULT_WARMUP_COOLDOWN_RATE: f64 = 0.25; +pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +pub struct Config { + /// how much stake we can activate/deactivate per-epoch as a fraction of currently effective stake + pub warmup_cooldown_rate: f64, + /// percentage of stake lost when slash, expressed as a portion of std::u8::MAX + pub slash_penalty: u8, +} + +impl Default for Config { + fn default() -> Self { + Self { + warmup_cooldown_rate: DEFAULT_WARMUP_COOLDOWN_RATE, + slash_penalty: DEFAULT_SLASH_PENALTY, + } + } +} diff --git a/sdk/program/src/stake/instruction.rs b/sdk/program/src/stake/instruction.rs new file mode 100644 index 0000000000..3b4782144d --- /dev/null +++ b/sdk/program/src/stake/instruction.rs @@ -0,0 +1,508 @@ +use { + crate::stake::{ + config, + program::id, + state::{Authorized, Lockup, StakeAuthorize, StakeState}, + }, + crate::{ + clock::{Epoch, UnixTimestamp}, + decode_error::DecodeError, + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_instruction, sysvar, + }, + log::*, + num_derive::{FromPrimitive, ToPrimitive}, + serde_derive::{Deserialize, Serialize}, + thiserror::Error, +}; + +/// Reasons the stake might have had an error +#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] +pub enum StakeError { + #[error("not enough credits to redeem")] + NoCreditsToRedeem, + + #[error("lockup has not yet expired")] + LockupInForce, + + #[error("stake already deactivated")] + AlreadyDeactivated, + + #[error("one re-delegation permitted per epoch")] + TooSoonToRedelegate, + + #[error("split amount is more than is staked")] + InsufficientStake, + + #[error("stake account with transient stake cannot be merged")] + MergeTransientStake, + + #[error("stake account merge failed due to different authority, lockups or state")] + MergeMismatch, + + #[error("custodian address not present")] + CustodianMissing, + + #[error("custodian signature not present")] + CustodianSignatureMissing, +} + +impl DecodeError for StakeError { + fn type_of() -> &'static str { + "StakeError" + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum StakeInstruction { + /// Initialize a stake with lockup and authorization information + /// + /// # Account references + /// 0. [WRITE] Uninitialized stake account + /// 1. [] Rent sysvar + /// + /// Authorized carries pubkeys that must sign staker transactions + /// and withdrawer transactions. + /// Lockup carries information about withdrawal restrictions + Initialize(Authorized, Lockup), + + /// Authorize a key to manage stake or withdrawal + /// + /// # Account references + /// 0. [WRITE] Stake account to be updated + /// 1. [] Clock sysvar + /// 2. [SIGNER] The stake or withdraw authority + /// 3. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before + /// lockup expiration + Authorize(Pubkey, StakeAuthorize), + + /// Delegate a stake to a particular vote account + /// + /// # Account references + /// 0. [WRITE] Initialized stake account to be delegated + /// 1. [] Vote account to which this stake will be delegated + /// 2. [] Clock sysvar + /// 3. [] Stake history sysvar that carries stake warmup/cooldown history + /// 4. [] Address of config account that carries stake config + /// 5. [SIGNER] Stake authority + /// + /// The entire balance of the staking account is staked. DelegateStake + /// can be called multiple times, but re-delegation is delayed + /// by one epoch + DelegateStake, + + /// Split u64 tokens and stake off a stake account into another stake account. + /// + /// # Account references + /// 0. [WRITE] Stake account to be split; must be in the Initialized or Stake state + /// 1. [WRITE] Uninitialized stake account that will take the split-off amount + /// 2. [SIGNER] Stake authority + Split(u64), + + /// Withdraw unstaked lamports from the stake account + /// + /// # Account references + /// 0. [WRITE] Stake account from which to withdraw + /// 1. [WRITE] Recipient account + /// 2. [] Clock sysvar + /// 3. [] Stake history sysvar that carries stake warmup/cooldown history + /// 4. [SIGNER] Withdraw authority + /// 5. Optional: [SIGNER] Lockup authority, if before lockup expiration + /// + /// The u64 is the portion of the stake account balance to be withdrawn, + /// must be `<= StakeAccount.lamports - staked_lamports`. + Withdraw(u64), + + /// Deactivates the stake in the account + /// + /// # Account references + /// 0. [WRITE] Delegated stake account + /// 1. [] Clock sysvar + /// 2. [SIGNER] Stake authority + Deactivate, + + /// Set stake lockup + /// + /// If a lockup is not active, the withdraw authority may set a new lockup + /// If a lockup is active, the lockup custodian may update the lockup parameters + /// + /// # Account references + /// 0. [WRITE] Initialized stake account + /// 1. [SIGNER] Lockup authority or withdraw authority + SetLockup(LockupArgs), + + /// Merge two stake accounts. + /// + /// Both accounts must have identical lockup and authority keys. A merge + /// is possible between two stakes in the following states with no additional + /// conditions: + /// + /// * two deactivated stakes + /// * an inactive stake into an activating stake during its activation epoch + /// + /// For the following cases, the voter pubkey and vote credits observed must match: + /// + /// * two activated stakes + /// * two activating accounts that share an activation epoch, during the activation epoch + /// + /// All other combinations of stake states will fail to merge, including all + /// "transient" states, where a stake is activating or deactivating with a + /// non-zero effective stake. + /// + /// # Account references + /// 0. [WRITE] Destination stake account for the merge + /// 1. [WRITE] Source stake account for to merge. This account will be drained + /// 2. [] Clock sysvar + /// 3. [] Stake history sysvar that carries stake warmup/cooldown history + /// 4. [SIGNER] Stake authority + Merge, + + /// Authorize a key to manage stake or withdrawal with a derived key + /// + /// # Account references + /// 0. [WRITE] Stake account to be updated + /// 1. [SIGNER] Base key of stake or withdraw authority + /// 2. [] Clock sysvar + /// 3. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before + /// lockup expiration + AuthorizeWithSeed(AuthorizeWithSeedArgs), +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] +pub struct LockupArgs { + pub unix_timestamp: Option, + pub epoch: Option, + pub custodian: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct AuthorizeWithSeedArgs { + pub new_authorized_pubkey: Pubkey, + pub stake_authorize: StakeAuthorize, + pub authority_seed: String, + pub authority_owner: Pubkey, +} + +pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction { + Instruction::new_with_bincode( + id(), + &StakeInstruction::Initialize(*authorized, *lockup), + vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ], + ) +} + +pub fn create_account_with_seed( + from_pubkey: &Pubkey, + stake_pubkey: &Pubkey, + base: &Pubkey, + seed: &str, + authorized: &Authorized, + lockup: &Lockup, + lamports: u64, +) -> Vec { + vec![ + system_instruction::create_account_with_seed( + from_pubkey, + stake_pubkey, + base, + seed, + lamports, + std::mem::size_of::() as u64, + &id(), + ), + initialize(stake_pubkey, authorized, lockup), + ] +} + +pub fn create_account( + from_pubkey: &Pubkey, + stake_pubkey: &Pubkey, + authorized: &Authorized, + lockup: &Lockup, + lamports: u64, +) -> Vec { + vec![ + system_instruction::create_account( + from_pubkey, + stake_pubkey, + lamports, + std::mem::size_of::() as u64, + &id(), + ), + initialize(stake_pubkey, authorized, lockup), + ] +} + +fn _split( + stake_pubkey: &Pubkey, + authorized_pubkey: &Pubkey, + lamports: u64, + split_stake_pubkey: &Pubkey, +) -> Instruction { + let account_metas = vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new(*split_stake_pubkey, false), + AccountMeta::new_readonly(*authorized_pubkey, true), + ]; + + Instruction::new_with_bincode(id(), &StakeInstruction::Split(lamports), account_metas) +} + +pub fn split( + stake_pubkey: &Pubkey, + authorized_pubkey: &Pubkey, + lamports: u64, + split_stake_pubkey: &Pubkey, +) -> Vec { + vec![ + system_instruction::allocate(split_stake_pubkey, std::mem::size_of::() as u64), + system_instruction::assign(split_stake_pubkey, &id()), + _split( + stake_pubkey, + authorized_pubkey, + lamports, + split_stake_pubkey, + ), + ] +} + +pub fn split_with_seed( + stake_pubkey: &Pubkey, + authorized_pubkey: &Pubkey, + lamports: u64, + split_stake_pubkey: &Pubkey, // derived using create_with_seed() + base: &Pubkey, // base + seed: &str, // seed +) -> Vec { + vec![ + system_instruction::allocate_with_seed( + split_stake_pubkey, + base, + seed, + std::mem::size_of::() as u64, + &id(), + ), + _split( + stake_pubkey, + authorized_pubkey, + lamports, + split_stake_pubkey, + ), + ] +} + +pub fn merge( + destination_stake_pubkey: &Pubkey, + source_stake_pubkey: &Pubkey, + authorized_pubkey: &Pubkey, +) -> Vec { + let account_metas = vec![ + AccountMeta::new(*destination_stake_pubkey, false), + AccountMeta::new(*source_stake_pubkey, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(sysvar::stake_history::id(), false), + AccountMeta::new_readonly(*authorized_pubkey, true), + ]; + + vec![Instruction::new_with_bincode( + id(), + &StakeInstruction::Merge, + account_metas, + )] +} + +pub fn create_account_and_delegate_stake( + from_pubkey: &Pubkey, + stake_pubkey: &Pubkey, + vote_pubkey: &Pubkey, + authorized: &Authorized, + lockup: &Lockup, + lamports: u64, +) -> Vec { + let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, lamports); + instructions.push(delegate_stake( + stake_pubkey, + &authorized.staker, + vote_pubkey, + )); + instructions +} + +pub fn create_account_with_seed_and_delegate_stake( + from_pubkey: &Pubkey, + stake_pubkey: &Pubkey, + base: &Pubkey, + seed: &str, + vote_pubkey: &Pubkey, + authorized: &Authorized, + lockup: &Lockup, + lamports: u64, +) -> Vec { + let mut instructions = create_account_with_seed( + from_pubkey, + stake_pubkey, + base, + seed, + authorized, + lockup, + lamports, + ); + instructions.push(delegate_stake( + stake_pubkey, + &authorized.staker, + vote_pubkey, + )); + instructions +} + +pub fn authorize( + stake_pubkey: &Pubkey, + authorized_pubkey: &Pubkey, + new_authorized_pubkey: &Pubkey, + stake_authorize: StakeAuthorize, + custodian_pubkey: Option<&Pubkey>, +) -> Instruction { + let mut account_metas = vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(*authorized_pubkey, true), + ]; + + if let Some(custodian_pubkey) = custodian_pubkey { + account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true)); + } + + Instruction::new_with_bincode( + id(), + &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize), + account_metas, + ) +} + +pub fn authorize_with_seed( + stake_pubkey: &Pubkey, + authority_base: &Pubkey, + authority_seed: String, + authority_owner: &Pubkey, + new_authorized_pubkey: &Pubkey, + stake_authorize: StakeAuthorize, + custodian_pubkey: Option<&Pubkey>, +) -> Instruction { + let mut account_metas = vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new_readonly(*authority_base, true), + AccountMeta::new_readonly(sysvar::clock::id(), false), + ]; + + if let Some(custodian_pubkey) = custodian_pubkey { + account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true)); + } + + let args = AuthorizeWithSeedArgs { + new_authorized_pubkey: *new_authorized_pubkey, + stake_authorize, + authority_seed, + authority_owner: *authority_owner, + }; + + Instruction::new_with_bincode( + id(), + &StakeInstruction::AuthorizeWithSeed(args), + account_metas, + ) +} + +pub fn delegate_stake( + stake_pubkey: &Pubkey, + authorized_pubkey: &Pubkey, + vote_pubkey: &Pubkey, +) -> Instruction { + let account_metas = vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new_readonly(*vote_pubkey, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(sysvar::stake_history::id(), false), + AccountMeta::new_readonly(config::id(), false), + AccountMeta::new_readonly(*authorized_pubkey, true), + ]; + Instruction::new_with_bincode(id(), &StakeInstruction::DelegateStake, account_metas) +} + +pub fn withdraw( + stake_pubkey: &Pubkey, + withdrawer_pubkey: &Pubkey, + to_pubkey: &Pubkey, + lamports: u64, + custodian_pubkey: Option<&Pubkey>, +) -> Instruction { + let mut account_metas = vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new(*to_pubkey, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(sysvar::stake_history::id(), false), + AccountMeta::new_readonly(*withdrawer_pubkey, true), + ]; + + if let Some(custodian_pubkey) = custodian_pubkey { + account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true)); + } + + Instruction::new_with_bincode(id(), &StakeInstruction::Withdraw(lamports), account_metas) +} + +pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction { + let account_metas = vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(*authorized_pubkey, true), + ]; + Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas) +} + +pub fn set_lockup( + stake_pubkey: &Pubkey, + lockup: &LockupArgs, + custodian_pubkey: &Pubkey, +) -> Instruction { + let account_metas = vec![ + AccountMeta::new(*stake_pubkey, false), + AccountMeta::new_readonly(*custodian_pubkey, true), + ]; + Instruction::new_with_bincode(id(), &StakeInstruction::SetLockup(*lockup), account_metas) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::instruction::InstructionError; + + #[test] + fn test_custom_error_decode() { + use num_traits::FromPrimitive; + fn pretty_err(err: InstructionError) -> String + where + T: 'static + std::error::Error + DecodeError + FromPrimitive, + { + if let InstructionError::Custom(code) = err { + let specific_error: T = T::decode_custom_error_to_enum(code).unwrap(); + format!( + "{:?}: {}::{:?} - {}", + err, + T::type_of(), + specific_error, + specific_error, + ) + } else { + "".to_string() + } + } + assert_eq!( + "Custom(0): StakeError::NoCreditsToRedeem - not enough credits to redeem", + pretty_err::(StakeError::NoCreditsToRedeem.into()) + ) + } +} diff --git a/sdk/program/src/stake/mod.rs b/sdk/program/src/stake/mod.rs new file mode 100644 index 0000000000..bb43a067f6 --- /dev/null +++ b/sdk/program/src/stake/mod.rs @@ -0,0 +1,7 @@ +pub mod config; +pub mod instruction; +pub mod state; + +pub mod program { + crate::declare_id!("Stake11111111111111111111111111111111111111"); +} diff --git a/sdk/program/src/stake/state.rs b/sdk/program/src/stake/state.rs new file mode 100644 index 0000000000..ed3096d005 --- /dev/null +++ b/sdk/program/src/stake/state.rs @@ -0,0 +1,533 @@ +#![allow(clippy::integer_arithmetic)] +use { + crate::{ + clock::{Clock, Epoch, UnixTimestamp}, + instruction::InstructionError, + pubkey::Pubkey, + rent::Rent, + stake::{ + config::Config, + instruction::{LockupArgs, StakeError}, + }, + stake_history::StakeHistory, + }, + std::collections::HashSet, +}; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] +#[allow(clippy::large_enum_variant)] +pub enum StakeState { + Uninitialized, + Initialized(Meta), + Stake(Meta, Stake), + RewardsPool, +} + +impl Default for StakeState { + fn default() -> Self { + StakeState::Uninitialized + } +} + +impl StakeState { + pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 { + rent.minimum_balance(std::mem::size_of::()) + } + + pub fn stake(&self) -> Option { + match self { + StakeState::Stake(_meta, stake) => Some(*stake), + _ => None, + } + } + + pub fn delegation(&self) -> Option { + match self { + StakeState::Stake(_meta, stake) => Some(stake.delegation), + _ => None, + } + } + + pub fn authorized(&self) -> Option { + match self { + StakeState::Stake(meta, _stake) => Some(meta.authorized), + StakeState::Initialized(meta) => Some(meta.authorized), + _ => None, + } + } + + pub fn lockup(&self) -> Option { + self.meta().map(|meta| meta.lockup) + } + + pub fn meta(&self) -> Option { + match self { + StakeState::Stake(meta, _stake) => Some(*meta), + StakeState::Initialized(meta) => Some(*meta), + _ => None, + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] +pub enum StakeAuthorize { + Staker, + Withdrawer, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] +pub struct Lockup { + /// UnixTimestamp at which this stake will allow withdrawal, unless the + /// transaction is signed by the custodian + pub unix_timestamp: UnixTimestamp, + /// epoch height at which this stake will allow withdrawal, unless the + /// transaction is signed by the custodian + pub epoch: Epoch, + /// custodian signature on a transaction exempts the operation from + /// lockup constraints + pub custodian: Pubkey, +} + +impl Lockup { + pub fn is_in_force(&self, clock: &Clock, custodian: Option<&Pubkey>) -> bool { + if custodian == Some(&self.custodian) { + return false; + } + self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch + } +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] +pub struct Authorized { + pub staker: Pubkey, + pub withdrawer: Pubkey, +} + +impl Authorized { + pub fn auto(authorized: &Pubkey) -> Self { + Self { + staker: *authorized, + withdrawer: *authorized, + } + } + pub fn check( + &self, + signers: &HashSet, + stake_authorize: StakeAuthorize, + ) -> Result<(), InstructionError> { + match stake_authorize { + StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()), + StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()), + _ => Err(InstructionError::MissingRequiredSignature), + } + } + + pub fn authorize( + &mut self, + signers: &HashSet, + new_authorized: &Pubkey, + stake_authorize: StakeAuthorize, + lockup_custodian_args: Option<(&Lockup, &Clock, Option<&Pubkey>)>, + ) -> Result<(), InstructionError> { + match stake_authorize { + StakeAuthorize::Staker => { + // Allow either the staker or the withdrawer to change the staker key + if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) { + return Err(InstructionError::MissingRequiredSignature); + } + self.staker = *new_authorized + } + StakeAuthorize::Withdrawer => { + if let Some((lockup, clock, custodian)) = lockup_custodian_args { + if lockup.is_in_force(&clock, None) { + match custodian { + None => { + return Err(StakeError::CustodianMissing.into()); + } + Some(custodian) => { + if !signers.contains(custodian) { + return Err(StakeError::CustodianSignatureMissing.into()); + } + + if lockup.is_in_force(&clock, Some(custodian)) { + return Err(StakeError::LockupInForce.into()); + } + } + } + } + } + self.check(signers, stake_authorize)?; + self.withdrawer = *new_authorized + } + } + Ok(()) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] +pub struct Meta { + pub rent_exempt_reserve: u64, + pub authorized: Authorized, + pub lockup: Lockup, +} + +impl Meta { + pub fn set_lockup( + &mut self, + lockup: &LockupArgs, + signers: &HashSet, + clock: Option<&Clock>, + ) -> Result<(), InstructionError> { + match clock { + None => { + // pre-stake_program_v4 behavior: custodian can set lockups at any time + if !signers.contains(&self.lockup.custodian) { + return Err(InstructionError::MissingRequiredSignature); + } + } + Some(clock) => { + // post-stake_program_v4 behavior: + // * custodian can update the lockup while in force + // * withdraw authority can set a new lockup + // + if self.lockup.is_in_force(clock, None) { + if !signers.contains(&self.lockup.custodian) { + return Err(InstructionError::MissingRequiredSignature); + } + } else if !signers.contains(&self.authorized.withdrawer) { + return Err(InstructionError::MissingRequiredSignature); + } + } + } + if let Some(unix_timestamp) = lockup.unix_timestamp { + self.lockup.unix_timestamp = unix_timestamp; + } + if let Some(epoch) = lockup.epoch { + self.lockup.epoch = epoch; + } + if let Some(custodian) = lockup.custodian { + self.lockup.custodian = custodian; + } + Ok(()) + } + + pub fn rewrite_rent_exempt_reserve( + &mut self, + rent: &Rent, + data_len: usize, + ) -> Option<(u64, u64)> { + let corrected_rent_exempt_reserve = rent.minimum_balance(data_len); + if corrected_rent_exempt_reserve != self.rent_exempt_reserve { + // We forcibly update rent_excempt_reserve even + // if rent_exempt_reserve > account_balance, hoping user might restore + // rent_exempt status by depositing. + let (old, new) = (self.rent_exempt_reserve, corrected_rent_exempt_reserve); + self.rent_exempt_reserve = corrected_rent_exempt_reserve; + Some((old, new)) + } else { + None + } + } + + pub fn auto(authorized: &Pubkey) -> Self { + Self { + authorized: Authorized::auto(authorized), + ..Meta::default() + } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] +pub struct Delegation { + /// to whom the stake is delegated + pub voter_pubkey: Pubkey, + /// activated stake amount, set at delegate() time + pub stake: u64, + /// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake + pub activation_epoch: Epoch, + /// epoch the stake was deactivated, std::Epoch::MAX if not deactivated + pub deactivation_epoch: Epoch, + /// how much stake we can activate per-epoch as a fraction of currently effective stake + pub warmup_cooldown_rate: f64, +} + +impl Default for Delegation { + fn default() -> Self { + Self { + voter_pubkey: Pubkey::default(), + stake: 0, + activation_epoch: 0, + deactivation_epoch: std::u64::MAX, + warmup_cooldown_rate: Config::default().warmup_cooldown_rate, + } + } +} + +impl Delegation { + pub fn new( + voter_pubkey: &Pubkey, + stake: u64, + activation_epoch: Epoch, + warmup_cooldown_rate: f64, + ) -> Self { + Self { + voter_pubkey: *voter_pubkey, + stake, + activation_epoch, + warmup_cooldown_rate, + ..Delegation::default() + } + } + pub fn is_bootstrap(&self) -> bool { + self.activation_epoch == std::u64::MAX + } + + pub fn stake( + &self, + epoch: Epoch, + history: Option<&StakeHistory>, + fix_stake_deactivate: bool, + ) -> u64 { + self.stake_activating_and_deactivating(epoch, history, fix_stake_deactivate) + .0 + } + + // returned tuple is (effective, activating, deactivating) stake + #[allow(clippy::comparison_chain)] + pub fn stake_activating_and_deactivating( + &self, + target_epoch: Epoch, + history: Option<&StakeHistory>, + fix_stake_deactivate: bool, + ) -> (u64, u64, u64) { + let delegated_stake = self.stake; + + // first, calculate an effective and activating stake + let (effective_stake, activating_stake) = + self.stake_and_activating(target_epoch, history, fix_stake_deactivate); + + // then de-activate some portion if necessary + if target_epoch < self.deactivation_epoch { + // not deactivated + (effective_stake, activating_stake, 0) + } else if target_epoch == self.deactivation_epoch { + // can only deactivate what's activated + (effective_stake, 0, effective_stake.min(delegated_stake)) + } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = + history.and_then(|history| { + history + .get(&self.deactivation_epoch) + .map(|cluster_stake_at_deactivation_epoch| { + ( + history, + self.deactivation_epoch, + cluster_stake_at_deactivation_epoch, + ) + }) + }) + { + // target_epoch > self.deactivation_epoch + + // loop from my deactivation epoch until the target epoch + // current effective stake is updated using its previous epoch's cluster stake + let mut current_epoch; + let mut current_effective_stake = effective_stake; + loop { + current_epoch = prev_epoch + 1; + // if there is no deactivating stake at prev epoch, we should have been + // fully undelegated at this moment + if prev_cluster_stake.deactivating == 0 { + break; + } + + // I'm trying to get to zero, how much of the deactivation in stake + // this account is entitled to take + let weight = + current_effective_stake as f64 / prev_cluster_stake.deactivating as f64; + + // portion of newly not-effective cluster stake I'm entitled to at current epoch + let newly_not_effective_cluster_stake = + prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate; + let newly_not_effective_stake = + ((weight * newly_not_effective_cluster_stake) as u64).max(1); + + current_effective_stake = + current_effective_stake.saturating_sub(newly_not_effective_stake); + if current_effective_stake == 0 { + break; + } + + if current_epoch >= target_epoch { + break; + } + if let Some(current_cluster_stake) = history.get(¤t_epoch) { + prev_epoch = current_epoch; + prev_cluster_stake = current_cluster_stake; + } else { + break; + } + } + + // deactivating stake should equal to all of currently remaining effective stake + (current_effective_stake, 0, current_effective_stake) + } else { + // no history or I've dropped out of history, so assume fully deactivated + (0, 0, 0) + } + } + + // returned tuple is (effective, activating) stake + fn stake_and_activating( + &self, + target_epoch: Epoch, + history: Option<&StakeHistory>, + fix_stake_deactivate: bool, + ) -> (u64, u64) { + let delegated_stake = self.stake; + + if self.is_bootstrap() { + // fully effective immediately + (delegated_stake, 0) + } else if fix_stake_deactivate && self.activation_epoch == self.deactivation_epoch { + // activated but instantly deactivated; no stake at all regardless of target_epoch + // this must be after the bootstrap check and before all-is-activating check + (0, 0) + } else if target_epoch == self.activation_epoch { + // all is activating + (0, delegated_stake) + } else if target_epoch < self.activation_epoch { + // not yet enabled + (0, 0) + } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) = + history.and_then(|history| { + history + .get(&self.activation_epoch) + .map(|cluster_stake_at_activation_epoch| { + ( + history, + self.activation_epoch, + cluster_stake_at_activation_epoch, + ) + }) + }) + { + // target_epoch > self.activation_epoch + + // loop from my activation epoch until the target epoch summing up my entitlement + // current effective stake is updated using its previous epoch's cluster stake + let mut current_epoch; + let mut current_effective_stake = 0; + loop { + current_epoch = prev_epoch + 1; + // if there is no activating stake at prev epoch, we should have been + // fully effective at this moment + if prev_cluster_stake.activating == 0 { + break; + } + + // how much of the growth in stake this account is + // entitled to take + let remaining_activating_stake = delegated_stake - current_effective_stake; + let weight = + remaining_activating_stake as f64 / prev_cluster_stake.activating as f64; + + // portion of newly effective cluster stake I'm entitled to at current epoch + let newly_effective_cluster_stake = + prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate; + let newly_effective_stake = + ((weight * newly_effective_cluster_stake) as u64).max(1); + + current_effective_stake += newly_effective_stake; + if current_effective_stake >= delegated_stake { + current_effective_stake = delegated_stake; + break; + } + + if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch { + break; + } + if let Some(current_cluster_stake) = history.get(¤t_epoch) { + prev_epoch = current_epoch; + prev_cluster_stake = current_cluster_stake; + } else { + break; + } + } + + ( + current_effective_stake, + delegated_stake - current_effective_stake, + ) + } else { + // no history or I've dropped out of history, so assume fully effective + (delegated_stake, 0) + } + } + + pub fn rewrite_stake( + &mut self, + account_balance: u64, + rent_exempt_balance: u64, + ) -> Option<(u64, u64)> { + // note that this will intentionally overwrite innocent + // deactivated-then-immeditealy-withdrawn stake accounts as well + // this is chosen to minimize the risks from complicated logic, + // over some unneeded rewrites + let corrected_stake = account_balance.saturating_sub(rent_exempt_balance); + if self.stake != corrected_stake { + // this could result in creating a 0-staked account; + // rewards and staking calc can handle it. + let (old, new) = (self.stake, corrected_stake); + self.stake = corrected_stake; + Some((old, new)) + } else { + None + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] +pub struct Stake { + pub delegation: Delegation, + /// credits observed is credits from vote account state when delegated or redeemed + pub credits_observed: u64, +} + +impl Stake { + pub fn stake( + &self, + epoch: Epoch, + history: Option<&StakeHistory>, + fix_stake_deactivate: bool, + ) -> u64 { + self.delegation.stake(epoch, history, fix_stake_deactivate) + } + + pub fn split( + &mut self, + remaining_stake_delta: u64, + split_stake_amount: u64, + ) -> Result { + if remaining_stake_delta > self.delegation.stake { + return Err(StakeError::InsufficientStake); + } + self.delegation.stake -= remaining_stake_delta; + let new = Self { + delegation: Delegation { + stake: split_stake_amount, + ..self.delegation + }, + ..*self + }; + Ok(new) + } + + pub fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> { + if self.delegation.deactivation_epoch != std::u64::MAX { + Err(StakeError::AlreadyDeactivated) + } else { + self.delegation.deactivation_epoch = epoch; + Ok(()) + } + } +} diff --git a/stake-accounts/src/main.rs b/stake-accounts/src/main.rs index 37a4da0cb8..2b478785ff 100644 --- a/stake-accounts/src/main.rs +++ b/stake-accounts/src/main.rs @@ -16,12 +16,10 @@ use solana_sdk::{ pubkey::Pubkey, signature::{unique_signers, Signature, Signer}, signers::Signers, + stake::{instruction::LockupArgs, state::Lockup}, transaction::Transaction, }; -use solana_stake_program::{ - stake_instruction::LockupArgs, - stake_state::{Lockup, StakeState}, -}; +use solana_stake_program::stake_state; use std::env; use std::error::Error; @@ -52,7 +50,7 @@ fn get_balances( fn get_lockup(client: &RpcClient, address: &Pubkey) -> Result { client .get_account(address) - .map(|account| StakeState::lockup_from(&account).unwrap()) + .map(|account| stake_state::lockup_from(&account).unwrap()) } fn get_lockups( diff --git a/stake-accounts/src/stake_accounts.rs b/stake-accounts/src/stake_accounts.rs index 86dd6f9569..37b4837408 100644 --- a/stake-accounts/src/stake_accounts.rs +++ b/stake-accounts/src/stake_accounts.rs @@ -1,16 +1,20 @@ use solana_sdk::{ - clock::SECONDS_PER_DAY, instruction::Instruction, message::Message, pubkey::Pubkey, -}; -use solana_stake_program::{ - stake_instruction::{self, LockupArgs}, - stake_state::{Authorized, Lockup, StakeAuthorize}, + clock::SECONDS_PER_DAY, + instruction::Instruction, + message::Message, + pubkey::Pubkey, + stake::{ + self, + instruction::{self as stake_instruction, LockupArgs}, + state::{Authorized, Lockup, StakeAuthorize}, + }, }; const DAYS_PER_YEAR: f64 = 365.25; const SECONDS_PER_YEAR: i64 = (SECONDS_PER_DAY as f64 * DAYS_PER_YEAR) as i64; pub(crate) fn derive_stake_account_address(base_pubkey: &Pubkey, i: usize) -> Pubkey { - Pubkey::create_with_seed(base_pubkey, &i.to_string(), &solana_stake_program::id()).unwrap() + Pubkey::create_with_seed(base_pubkey, &i.to_string(), &stake::program::id()).unwrap() } // Return derived addresses @@ -284,8 +288,9 @@ mod tests { client::SyncClient, genesis_config::create_genesis_config, signature::{Keypair, Signer}, + stake::state::StakeState, }; - use solana_stake_program::stake_state::StakeState; + use solana_stake_program::stake_state; fn create_bank(lamports: u64) -> (Bank, Keypair, u64) { let (genesis_config, mint_keypair) = create_genesis_config(lamports); @@ -338,7 +343,7 @@ mod tests { let address = derive_stake_account_address(&base_pubkey, i); let account = AccountSharedData::from(client.get_account(&address).unwrap().unwrap()); - (address, StakeState::lockup_from(&account).unwrap()) + (address, stake_state::lockup_from(&account).unwrap()) }) .collect() } @@ -375,7 +380,7 @@ mod tests { let account = get_account_at(&bank_client, &base_pubkey, 0); assert_eq!(account.lamports(), lamports); - let authorized = StakeState::authorized_from(&account).unwrap(); + let authorized = stake_state::authorized_from(&account).unwrap(); assert_eq!(authorized.staker, stake_authority_pubkey); assert_eq!(authorized.withdrawer, withdraw_authority_pubkey); } @@ -437,7 +442,7 @@ mod tests { } let account = get_account_at(&bank_client, &base_pubkey, 0); - let authorized = StakeState::authorized_from(&account).unwrap(); + let authorized = stake_state::authorized_from(&account).unwrap(); assert_eq!(authorized.staker, new_stake_authority_pubkey); assert_eq!(authorized.withdrawer, new_withdraw_authority_pubkey); } @@ -493,7 +498,7 @@ mod tests { } let account = get_account_at(&bank_client, &base_pubkey, 0); - let lockup = StakeState::lockup_from(&account).unwrap(); + let lockup = stake_state::lockup_from(&account).unwrap(); assert_eq!(lockup.unix_timestamp, 1); assert_eq!(lockup.epoch, 0); @@ -586,7 +591,7 @@ mod tests { // Ensure the new accounts are duplicates of the previous ones. let account = get_account_at(&bank_client, &new_base_pubkey, 0); - let authorized = StakeState::authorized_from(&account).unwrap(); + let authorized = stake_state::authorized_from(&account).unwrap(); assert_eq!(authorized.staker, stake_authority_pubkey); assert_eq!(authorized.withdrawer, withdraw_authority_pubkey); } @@ -655,7 +660,7 @@ mod tests { // Ensure the new accounts have the new authorities. let account = get_account_at(&bank_client, &new_base_pubkey, 0); - let authorized = StakeState::authorized_from(&account).unwrap(); + let authorized = stake_state::authorized_from(&account).unwrap(); assert_eq!(authorized.staker, new_stake_authority_pubkey); assert_eq!(authorized.withdrawer, new_withdraw_authority_pubkey); } diff --git a/stake-monitor/Cargo.toml b/stake-monitor/Cargo.toml index 9b576cd36e..202373f243 100644 --- a/stake-monitor/Cargo.toml +++ b/stake-monitor/Cargo.toml @@ -22,7 +22,6 @@ solana-logger = { path = "../logger", version = "=1.8.0" } solana-metrics = { path = "../metrics", version = "=1.8.0" } solana-rpc = { path = "../rpc", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-transaction-status = { path = "../transaction-status", version = "=1.8.0" } solana-version = { path = "../version", version = "=1.8.0" } diff --git a/stake-monitor/src/lib.rs b/stake-monitor/src/lib.rs index c6a27e7865..a16168a731 100644 --- a/stake-monitor/src/lib.rs +++ b/stake-monitor/src/lib.rs @@ -4,10 +4,14 @@ use serde::{Deserialize, Serialize}; use solana_client::{client_error::Result as ClientResult, rpc_client::RpcClient}; use solana_metrics::{datapoint_error, datapoint_info}; use solana_sdk::{ - clock::Slot, native_token::LAMPORTS_PER_SOL, program_utils::limited_deserialize, - pubkey::Pubkey, signature::Signature, transaction::Transaction, + clock::Slot, + native_token::LAMPORTS_PER_SOL, + program_utils::limited_deserialize, + pubkey::Pubkey, + signature::Signature, + stake::{self, instruction::StakeInstruction, state::Lockup}, + transaction::Transaction, }; -use solana_stake_program::{stake_instruction::StakeInstruction, stake_state::Lockup}; use solana_transaction_status::{ EncodedConfirmedBlock, UiTransactionEncoding, UiTransactionStatusMeta, }; @@ -78,7 +82,7 @@ fn process_transaction( // Look for stake operations for instruction in message.instructions.iter().rev() { let program_pubkey = message.account_keys[instruction.program_id_index as usize]; - if program_pubkey != solana_stake_program::id() { + if program_pubkey != stake::program::id() { continue; } @@ -368,10 +372,10 @@ mod test { message::Message, native_token::sol_to_lamports, signature::{Keypair, Signer}, + stake::{instruction as stake_instruction, state::Authorized}, system_transaction, transaction::Transaction, }; - use solana_stake_program::{stake_instruction, stake_state::Authorized}; #[test] #[serial] diff --git a/tokens/Cargo.toml b/tokens/Cargo.toml index b965965af8..b6f0322ea7 100644 --- a/tokens/Cargo.toml +++ b/tokens/Cargo.toml @@ -27,7 +27,6 @@ solana-client = { path = "../client", version = "=1.8.0" } solana-remote-wallet = { path = "../remote-wallet", version = "=1.8.0" } solana-runtime = { path = "../runtime", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-transaction-status = { path = "../transaction-status", version = "=1.8.0" } solana-version = { path = "../version", version = "=1.8.0" } spl-associated-token-account-v1-0 = { package = "spl-associated-token-account", version = "=1.0.2" } diff --git a/tokens/src/commands.rs b/tokens/src/commands.rs index d512bf90fe..67dbc2bbe5 100644 --- a/tokens/src/commands.rs +++ b/tokens/src/commands.rs @@ -27,13 +27,13 @@ use solana_sdk::{ message::Message, native_token::{lamports_to_sol, sol_to_lamports}, signature::{unique_signers, Signature, Signer}, + stake::{ + instruction::{self as stake_instruction, LockupArgs}, + state::{Authorized, Lockup, StakeAuthorize}, + }, system_instruction, transaction::Transaction, }; -use solana_stake_program::{ - stake_instruction::{self, LockupArgs}, - stake_state::{Authorized, Lockup, StakeAuthorize}, -}; use solana_transaction_status::TransactionStatus; use spl_associated_token_account_v1_0::get_associated_token_address; use spl_token_v2_0::solana_program::program_error::ProgramError; @@ -1204,8 +1204,10 @@ pub fn test_process_distribute_stake_with_client(client: &RpcClient, sender_keyp mod tests { use super::*; use solana_core::test_validator::TestValidator; - use solana_sdk::signature::{read_keypair_file, write_keypair_file, Signer}; - use solana_stake_program::stake_instruction::StakeInstruction; + use solana_sdk::{ + signature::{read_keypair_file, write_keypair_file, Signer}, + stake::instruction::StakeInstruction, + }; use solana_transaction_status::TransactionConfirmationStatus; #[test] diff --git a/transaction-status/Cargo.toml b/transaction-status/Cargo.toml index 22a8398883..665160efaa 100644 --- a/transaction-status/Cargo.toml +++ b/transaction-status/Cargo.toml @@ -21,7 +21,6 @@ serde_json = "1.0.64" solana-account-decoder = { path = "../account-decoder", version = "=1.8.0" } solana-sdk = { path = "../sdk", version = "=1.8.0" } solana-runtime = { path = "../runtime", version = "=1.8.0" } -solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } solana-vote-program = { path = "../programs/vote", version = "=1.8.0" } spl-associated-token-account-v1-0 = { package = "spl-associated-token-account", version = "=1.0.2", features = ["no-entrypoint"] } spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] } diff --git a/transaction-status/src/parse_instruction.rs b/transaction-status/src/parse_instruction.rs index 4e2874ee54..15b5b7bf07 100644 --- a/transaction-status/src/parse_instruction.rs +++ b/transaction-status/src/parse_instruction.rs @@ -9,7 +9,7 @@ use crate::{ use inflector::Inflector; use serde_json::Value; use solana_account_decoder::parse_token::spl_token_id_v2_0; -use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey, system_program}; +use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey, stake, system_program}; use std::{collections::HashMap, str::from_utf8}; use thiserror::Error; @@ -19,7 +19,7 @@ lazy_static! { static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id(); static ref MEMO_V1_PROGRAM_ID: Pubkey = Pubkey::new_from_array(spl_memo::v1::id().to_bytes()); static ref MEMO_V3_PROGRAM_ID: Pubkey = Pubkey::new_from_array(spl_memo::id().to_bytes()); - static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id(); + static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id(); static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id(); static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0(); static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id(); diff --git a/transaction-status/src/parse_stake.rs b/transaction-status/src/parse_stake.rs index 1f733459e3..d6a6c2332c 100644 --- a/transaction-status/src/parse_stake.rs +++ b/transaction-status/src/parse_stake.rs @@ -3,8 +3,9 @@ use crate::parse_instruction::{ }; use bincode::deserialize; use serde_json::{json, Map}; -use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; -use solana_stake_program::stake_instruction::StakeInstruction; +use solana_sdk::{ + instruction::CompiledInstruction, pubkey::Pubkey, stake::instruction::StakeInstruction, +}; pub fn parse_stake( instruction: &CompiledInstruction, @@ -195,10 +196,13 @@ fn check_num_stake_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInst #[cfg(test)] mod test { use super::*; - use solana_sdk::{message::Message, pubkey::Pubkey}; - use solana_stake_program::{ - stake_instruction::{self, LockupArgs}, - stake_state::{Authorized, Lockup, StakeAuthorize}, + use solana_sdk::{ + message::Message, + pubkey::Pubkey, + stake::{ + instruction::{self, LockupArgs}, + state::{Authorized, Lockup, StakeAuthorize}, + }, }; #[test] @@ -221,7 +225,7 @@ mod test { let lamports = 55; let instructions = - stake_instruction::create_account(&keys[0], &keys[1], &authorized, &lockup, lamports); + instruction::create_account(&keys[0], &keys[1], &authorized, &lockup, lamports); let message = Message::new(&instructions, None); assert_eq!( parse_stake(&message.instructions[1], &keys[0..3]).unwrap(), @@ -244,13 +248,8 @@ mod test { ); assert!(parse_stake(&message.instructions[1], &keys[0..2]).is_err()); - let instruction = stake_instruction::authorize( - &keys[1], - &keys[0], - &keys[3], - StakeAuthorize::Staker, - None, - ); + let instruction = + instruction::authorize(&keys[1], &keys[0], &keys[3], StakeAuthorize::Staker, None); let message = Message::new(&[instruction], None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..3]).unwrap(), @@ -267,7 +266,7 @@ mod test { ); assert!(parse_stake(&message.instructions[0], &keys[0..2]).is_err()); - let instruction = stake_instruction::authorize( + let instruction = instruction::authorize( &keys[1], &keys[0], &keys[3], @@ -291,7 +290,7 @@ mod test { ); assert!(parse_stake(&message.instructions[0], &keys[0..2]).is_err()); - let instruction = stake_instruction::delegate_stake(&keys[1], &keys[0], &keys[2]); + let instruction = instruction::delegate_stake(&keys[1], &keys[0], &keys[2]); let message = Message::new(&[instruction], None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..6]).unwrap(), @@ -313,7 +312,7 @@ mod test { // * split account (signer, allocate + assign first) // * stake authority (signer) // * stake account - let instructions = stake_instruction::split(&keys[2], &keys[1], lamports, &keys[0]); + let instructions = instruction::split(&keys[2], &keys[1], lamports, &keys[0]); let message = Message::new(&instructions, None); assert_eq!( parse_stake(&message.instructions[2], &keys[0..3]).unwrap(), @@ -329,7 +328,7 @@ mod test { ); assert!(parse_stake(&message.instructions[2], &keys[0..2]).is_err()); - let instruction = stake_instruction::withdraw(&keys[1], &keys[0], &keys[2], lamports, None); + let instruction = instruction::withdraw(&keys[1], &keys[0], &keys[2], lamports, None); let message = Message::new(&[instruction], None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..5]).unwrap(), @@ -346,7 +345,7 @@ mod test { } ); let instruction = - stake_instruction::withdraw(&keys[2], &keys[0], &keys[3], lamports, Some(&keys[1])); + instruction::withdraw(&keys[2], &keys[0], &keys[3], lamports, Some(&keys[1])); let message = Message::new(&[instruction], None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..6]).unwrap(), @@ -365,7 +364,7 @@ mod test { ); assert!(parse_stake(&message.instructions[0], &keys[0..4]).is_err()); - let instruction = stake_instruction::deactivate_stake(&keys[1], &keys[0]); + let instruction = instruction::deactivate_stake(&keys[1], &keys[0]); let message = Message::new(&[instruction], None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..3]).unwrap(), @@ -380,7 +379,7 @@ mod test { ); assert!(parse_stake(&message.instructions[0], &keys[0..2]).is_err()); - let instructions = stake_instruction::merge(&keys[1], &keys[0], &keys[2]); + let instructions = instruction::merge(&keys[1], &keys[0], &keys[2]); let message = Message::new(&instructions, None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..5]).unwrap(), @@ -398,7 +397,7 @@ mod test { assert!(parse_stake(&message.instructions[0], &keys[0..4]).is_err()); let seed = "test_seed"; - let instruction = stake_instruction::authorize_with_seed( + let instruction = instruction::authorize_with_seed( &keys[1], &keys[0], seed.to_string(), @@ -425,7 +424,7 @@ mod test { ); assert!(parse_stake(&message.instructions[0], &keys[0..1]).is_err()); - let instruction = stake_instruction::authorize_with_seed( + let instruction = instruction::authorize_with_seed( &keys[1], &keys[0], seed.to_string(), @@ -470,7 +469,7 @@ mod test { epoch: None, custodian: None, }; - let instruction = stake_instruction::set_lockup(&keys[1], &lockup, &keys[0]); + let instruction = instruction::set_lockup(&keys[1], &lockup, &keys[0]); let message = Message::new(&[instruction], None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..2]).unwrap(), @@ -491,7 +490,7 @@ mod test { epoch: Some(epoch), custodian: None, }; - let instruction = stake_instruction::set_lockup(&keys[1], &lockup, &keys[0]); + let instruction = instruction::set_lockup(&keys[1], &lockup, &keys[0]); let message = Message::new(&[instruction], None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..2]).unwrap(), @@ -513,7 +512,7 @@ mod test { epoch: Some(epoch), custodian: Some(custodian), }; - let instruction = stake_instruction::set_lockup(&keys[1], &lockup, &keys[0]); + let instruction = instruction::set_lockup(&keys[1], &lockup, &keys[0]); let message = Message::new(&[instruction], None); assert_eq!( parse_stake(&message.instructions[0], &keys[0..2]).unwrap(),