diff --git a/Cargo.lock b/Cargo.lock index e1f9658078..b756d9b3cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3739,6 +3739,8 @@ version = "0.19.0-pre0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3866,6 +3868,8 @@ version = "0.19.0-pre0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "solana-logger 0.19.0-pre0", diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index f03436ee40..8c9c9a1120 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -28,7 +28,7 @@ use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil, Signature}; use solana_sdk::system_instruction::SystemError; use solana_sdk::system_transaction; use solana_sdk::transaction::{Transaction, TransactionError}; -use solana_stake_api::stake_instruction; +use solana_stake_api::{stake_instruction, stake_state::StakeError}; use solana_storage_api::storage_instruction; use solana_vote_api::vote_instruction; use solana_vote_api::vote_state::VoteState; @@ -586,8 +586,7 @@ fn process_create_vote_account( let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); - let signature_str = log_instruction_custom_error::(result)?; - Ok(signature_str.to_string()) + log_instruction_custom_error::(result) } fn process_authorize_voter( @@ -733,9 +732,9 @@ fn process_deactivate_stake_account( recent_blockhash, ); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; - let signature_str = rpc_client - .send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair])?; - Ok(signature_str.to_string()) + let result = rpc_client + .send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]); + log_instruction_custom_error::(result) } fn process_delegate_stake( @@ -812,8 +811,7 @@ fn process_delegate_stake( let result = rpc_client .send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]); - let signature_str = log_instruction_custom_error::(result)?; - Ok(signature_str.to_string()) + log_instruction_custom_error::(result) } fn process_withdraw_stake( @@ -838,9 +836,9 @@ fn process_withdraw_stake( ); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; - let signature_str = rpc_client - .send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair])?; - Ok(signature_str.to_string()) + let result = rpc_client + .send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]); + log_instruction_custom_error::(result) } fn process_redeem_vote_credits( @@ -861,8 +859,8 @@ fn process_redeem_vote_credits( recent_blockhash, ); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; - let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair])?; - Ok(signature_str.to_string()) + let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); + log_instruction_custom_error::(result) } fn process_show_stake_account( @@ -931,8 +929,7 @@ fn process_create_replicator_storage_account( let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); - let signature_str = log_instruction_custom_error::(result)?; - Ok(signature_str.to_string()) + log_instruction_custom_error::(result) } fn process_create_validator_storage_account( @@ -958,8 +955,7 @@ fn process_create_validator_storage_account( let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); - let signature_str = log_instruction_custom_error::(result)?; - Ok(signature_str.to_string()) + log_instruction_custom_error::(result) } fn process_claim_storage_reward( @@ -1104,8 +1100,7 @@ fn process_pay( let mut tx = system_transaction::transfer(&config.keypair, to, lamports, blockhash); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); - let signature_str = log_instruction_custom_error::(result)?; - Ok(signature_str.to_string()) + log_instruction_custom_error::(result) } else if *witnesses == None { let dt = timestamp.unwrap(); let dt_pubkey = match timestamp_pubkey { @@ -1182,8 +1177,7 @@ fn process_cancel(rpc_client: &RpcClient, config: &WalletConfig, pubkey: &Pubkey let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); - let signature_str = log_instruction_custom_error::(result)?; - Ok(signature_str.to_string()) + log_instruction_custom_error::(result) } fn process_get_slot(rpc_client: &RpcClient) -> ProcessResult { @@ -1209,9 +1203,7 @@ fn process_time_elapsed( let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); - let signature_str = log_instruction_custom_error::(result)?; - - Ok(signature_str.to_string()) + log_instruction_custom_error::(result) } fn process_witness( @@ -1226,9 +1218,7 @@ fn process_witness( let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); - let signature_str = log_instruction_custom_error::(result)?; - - Ok(signature_str.to_string()) + log_instruction_custom_error::(result) } fn process_get_version(rpc_client: &RpcClient, config: &WalletConfig) -> ProcessResult { @@ -1634,7 +1624,7 @@ pub fn request_and_confirm_airdrop( drone_addr: &SocketAddr, to_pubkey: &Pubkey, lamports: u64, -) -> Result<(), Box> { +) -> ProcessResult { let (blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; let keypair = { let mut retries = 5; @@ -1649,8 +1639,7 @@ pub fn request_and_confirm_airdrop( }?; let mut tx = keypair.airdrop_transaction(); let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&keypair]); - log_instruction_custom_error::(result)?; - Ok(()) + log_instruction_custom_error::(result) } fn log_instruction_custom_error(result: Result) -> ProcessResult @@ -1665,12 +1654,7 @@ where )) = err { if let Some(specific_error) = E::decode_custom_error_to_enum(code) { - error!( - "{:?}: {}::{:?}", - err, - specific_error.type_of(), - specific_error - ); + error!("{}::{:?}", E::type_of(), specific_error); Err(specific_error)? } } diff --git a/programs/budget_api/src/budget_state.rs b/programs/budget_api/src/budget_state.rs index c7c0390497..c51ae264d8 100644 --- a/programs/budget_api/src/budget_state.rs +++ b/programs/budget_api/src/budget_state.rs @@ -12,7 +12,7 @@ pub enum BudgetError { } impl DecodeError for BudgetError { - fn type_of(&self) -> &'static str { + fn type_of() -> &'static str { "BudgetError" } } diff --git a/programs/stake_api/Cargo.toml b/programs/stake_api/Cargo.toml index 1a6003fcb9..cfbe768ab1 100644 --- a/programs/stake_api/Cargo.toml +++ b/programs/stake_api/Cargo.toml @@ -11,6 +11,8 @@ edition = "2018" [dependencies] bincode = "1.1.4" log = "0.4.8" +num-derive = "0.2" +num-traits = "0.2" rand = "0.6.5" serde = "1.0.99" serde_derive = "1.0.99" diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 220a98e3df..a6ea435839 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -4,11 +4,13 @@ //! * own mining pools use crate::{config::Config, id}; +use num_derive::{FromPrimitive, ToPrimitive}; use serde_derive::{Deserialize, Serialize}; use solana_sdk::{ account::{Account, KeyedAccount}, account_utils::State, instruction::InstructionError, + instruction_processor_utils::DecodeError, pubkey::Pubkey, sysvar::{ self, @@ -50,6 +52,25 @@ impl StakeState { } } +/// Reasons the stake might have had an error +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] +pub enum StakeError { + NoCreditsToRedeem, +} +impl DecodeError for StakeError { + fn type_of() -> &'static str { + "StakeError" + } +} +impl std::fmt::Display for StakeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + StakeError::NoCreditsToRedeem => write!(f, "not enough credits to redeem"), + } + } +} +impl std::error::Error for StakeError {} + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct Stake { pub voter_pubkey: Pubkey, @@ -408,7 +429,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { self.set_state(&StakeState::Stake(stake)) } else { // not worth collecting - Err(InstructionError::CustomError(1)) + Err(StakeError::NoCreditsToRedeem.into()) } } else { Err(InstructionError::InvalidAccountData) @@ -1236,6 +1257,32 @@ mod tests { ); } + #[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::CustomError(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!( + "CustomError(0): StakeError::NoCreditsToRedeem - not enough credits to redeem", + pretty_err::(StakeError::NoCreditsToRedeem.into()) + ) + } + #[test] fn test_stake_state_calculate_rewards() { let mut vote_state = VoteState::default(); @@ -1379,7 +1426,7 @@ mod tests { &rewards, &stake_history, ), - Err(InstructionError::CustomError(1)) + Err(StakeError::NoCreditsToRedeem.into()) ); // in this call, we've swapped rewards and vote, deserialization of rewards_pool fails diff --git a/programs/token_api/src/token_state.rs b/programs/token_api/src/token_state.rs index 9b2a3ee4a7..cb73dcd011 100644 --- a/programs/token_api/src/token_state.rs +++ b/programs/token_api/src/token_state.rs @@ -13,7 +13,7 @@ pub enum TokenError { } impl DecodeError for TokenError { - fn type_of(&self) -> &'static str { + fn type_of() -> &'static str { "TokenError" } } diff --git a/programs/vote_api/Cargo.toml b/programs/vote_api/Cargo.toml index 76d96140e0..3102ecff51 100644 --- a/programs/vote_api/Cargo.toml +++ b/programs/vote_api/Cargo.toml @@ -11,6 +11,8 @@ edition = "2018" [dependencies] bincode = "1.1.4" log = "0.4.8" +num-derive = "0.2" +num-traits = "0.2" serde = "1.0.99" serde_derive = "1.0.99" solana-logger = { path = "../../logger", version = "0.19.0-pre0" } diff --git a/programs/vote_api/src/vote_state.rs b/programs/vote_api/src/vote_state.rs index d9b75457e3..e74b31bf85 100644 --- a/programs/vote_api/src/vote_state.rs +++ b/programs/vote_api/src/vote_state.rs @@ -3,14 +3,18 @@ use crate::id; use bincode::{deserialize, serialize_into, serialized_size, ErrorKind}; use log::*; +use num_derive::{FromPrimitive, ToPrimitive}; use serde_derive::{Deserialize, Serialize}; -use solana_sdk::account::{Account, KeyedAccount}; -use solana_sdk::account_utils::State; -use solana_sdk::hash::Hash; -use solana_sdk::instruction::InstructionError; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::sysvar::clock::Clock; -pub use solana_sdk::timing::{Epoch, Slot}; +use solana_sdk::{ + account::{Account, KeyedAccount}, + account_utils::State, + hash::Hash, + instruction::InstructionError, + instruction_processor_utils::DecodeError, + pubkey::Pubkey, + sysvar::clock::Clock, + timing::{Epoch, Slot}, +}; use std::collections::VecDeque; // Maximum number of votes to keep around @@ -21,6 +25,36 @@ pub const INITIAL_LOCKOUT: usize = 2; // smaller numbers makes pub const MAX_EPOCH_CREDITS_HISTORY: usize = 64; +/// Reasons the stake might have had an error +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] +pub enum VoteError { + VoteTooOld, + SlotsMismatch, + SlotHashMismatch, + EmptySlots, +} +impl DecodeError for VoteError { + fn type_of() -> &'static str { + "VoteError" + } +} + +impl std::fmt::Display for VoteError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}", + match self { + VoteError::VoteTooOld => "vote already recorded or not in slot hashes history", + VoteError::SlotsMismatch => "vote slots do not match bank history", + VoteError::SlotHashMismatch => "vote hash does not match bank hash", + VoteError::EmptySlots => "vote has no slots, invalid", + } + ) + } +} +impl std::error::Error for VoteError {} + #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Vote { /// A stack of votes starting with the oldest vote @@ -147,10 +181,15 @@ impl VoteState { } } } - fn check_slots_are_valid(&self, vote: &Vote, slot_hashes: &[(Slot, Hash)]) -> bool { - let mut i = 0; - let mut j = slot_hashes.len(); + fn check_slots_are_valid( + &self, + vote: &Vote, + slot_hashes: &[(Slot, Hash)], + ) -> Result<(), VoteError> { + let mut i = 0; // index into the vote's slots + let mut j = slot_hashes.len(); // index into the slot_hashes while i < vote.slots.len() && j > 0 { + // find the most recent "new" slot in the vote if self .votes .back() @@ -168,36 +207,40 @@ impl VoteState { } if j == slot_hashes.len() { warn!( - "{} dropped vote {:?} to old: {:?} ", + "{} dropped vote {:?} too old: {:?} ", self.node_pubkey, vote, slot_hashes ); - return false; + return Err(VoteError::VoteTooOld); } if i != vote.slots.len() { warn!( "{} dropped vote {:?} failed to match slot: {:?}", self.node_pubkey, vote, slot_hashes, ); - - return false; + return Err(VoteError::SlotsMismatch); } if slot_hashes[j].1 != vote.hash { warn!( "{} dropped vote {:?} failed to match hash {} {}", self.node_pubkey, vote, vote.hash, slot_hashes[j].1 ); - return false; + return Err(VoteError::SlotHashMismatch); } - true + Ok(()) } - pub fn process_vote(&mut self, vote: &Vote, slot_hashes: &[(Slot, Hash)], epoch: Epoch) { + pub fn process_vote( + &mut self, + vote: &Vote, + slot_hashes: &[(Slot, Hash)], + epoch: Epoch, + ) -> Result<(), VoteError> { if vote.slots.is_empty() { - return; - } - if !self.check_slots_are_valid(vote, slot_hashes) { - return; + return Err(VoteError::EmptySlots); } + self.check_slots_are_valid(vote, slot_hashes)?; + vote.slots.iter().for_each(|s| self.process_slot(*s, epoch)); + Ok(()) } pub fn process_slot(&mut self, slot: Slot, epoch: Epoch) { @@ -249,7 +292,7 @@ impl VoteState { /// "unchecked" functions used by tests and Tower pub fn process_vote_unchecked(&mut self, vote: &Vote) { let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect(); - self.process_vote(vote, &slot_hashes, self.epoch); + let _ignored = self.process_vote(vote, &slot_hashes, self.epoch); } pub fn process_slot_vote_unchecked(&mut self, slot: Slot) { self.process_vote_unchecked(&Vote::new(vec![slot], Hash::default())); @@ -272,7 +315,9 @@ impl VoteState { /// Number of "credits" owed to this account from the mining pool on a per-epoch basis, /// starting from credits observed. - /// Each tuple of (Epoch, u64) is the credits() delta as of the end of the Epoch + /// Each tuple of (Epoch, u64, u64) is read as (epoch, credits, prev_credits), where + /// credits for each epoch is credits - prev_credits; while redundant this makes + /// calculating rewards over partial epochs nice and simple pub fn epoch_credits(&self) -> impl Iterator { self.epoch_credits.iter() } @@ -383,7 +428,7 @@ pub fn process_vote( return Err(InstructionError::MissingRequiredSignature); } - vote_state.process_vote(vote, slot_hashes, clock.epoch); + vote_state.process_vote(vote, slot_hashes, clock.epoch)?; vote_account.set_state(&vote_state) } @@ -516,25 +561,28 @@ mod tests { let vote = Vote::new(vec![0], hash); // wrong hash - let vote_state = simulate_process_vote( - &vote_pubkey, - &mut vote_account, - &vote, - &[(0, Hash::default())], - 0, - ) - .unwrap(); - assert_eq!(vote_state.votes.len(), 0); + assert_eq!( + simulate_process_vote( + &vote_pubkey, + &mut vote_account, + &vote, + &[(0, Hash::default())], + 0, + ), + Err(VoteError::SlotHashMismatch.into()) + ); // wrong slot - let vote_state = - simulate_process_vote(&vote_pubkey, &mut vote_account, &vote, &[(1, hash)], 0).unwrap(); - assert_eq!(vote_state.votes.len(), 0); + assert_eq!( + simulate_process_vote(&vote_pubkey, &mut vote_account, &vote, &[(1, hash)], 0), + Err(VoteError::SlotsMismatch.into()) + ); // empty slot_hashes - let vote_state = - simulate_process_vote(&vote_pubkey, &mut vote_account, &vote, &[], 0).unwrap(); - assert_eq!(vote_state.votes.len(), 0); + assert_eq!( + simulate_process_vote(&vote_pubkey, &mut vote_account, &vote, &[], 0), + Err(VoteError::VoteTooOld.into()) + ); } #[test] @@ -797,8 +845,8 @@ mod tests { let vote = Vote::new(slots, Hash::default()); let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect(); - vote_state_a.process_vote(&vote, &slot_hashes, 0); - vote_state_b.process_vote(&vote, &slot_hashes, 0); + assert_eq!(vote_state_a.process_vote(&vote, &slot_hashes, 0), Ok(())); + assert_eq!(vote_state_b.process_vote(&vote, &slot_hashes, 0), Ok(())); assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b)); } @@ -807,9 +855,14 @@ mod tests { let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); let vote = Vote::new(vec![0], Hash::default()); - let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap() + 1, vote.hash)]; - vote_state.process_vote(&vote, &slot_hashes, 0); - assert!(recent_votes(&vote_state).is_empty()); + let slot_hashes: Vec<_> = vec![(0, vote.hash)]; + assert_eq!(vote_state.process_vote(&vote, &slot_hashes, 0), Ok(())); + let recent = recent_votes(&vote_state); + assert_eq!( + vote_state.process_vote(&vote, &slot_hashes, 0), + Err(VoteError::VoteTooOld) + ); + assert_eq!(recent, recent_votes(&vote_state)); } #[test] @@ -817,7 +870,10 @@ mod tests { let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); let vote = Vote::new(vec![0], Hash::default()); - assert_eq!(vote_state.check_slots_are_valid(&vote, &vec![]), false); + assert_eq!( + vote_state.check_slots_are_valid(&vote, &vec![]), + Err(VoteError::VoteTooOld) + ); } #[test] @@ -826,7 +882,10 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; - assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), true); + assert_eq!( + vote_state.check_slots_are_valid(&vote, &slot_hashes), + Ok(()) + ); } #[test] @@ -835,7 +894,10 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))]; - assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), false); + assert_eq!( + vote_state.check_slots_are_valid(&vote, &slot_hashes), + Err(VoteError::SlotHashMismatch) + ); } #[test] @@ -844,7 +906,10 @@ mod tests { let vote = Vote::new(vec![1], Hash::default()); let slot_hashes: Vec<_> = vec![(0, vote.hash)]; - assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), false); + assert_eq!( + vote_state.check_slots_are_valid(&vote, &slot_hashes), + Err(VoteError::SlotsMismatch) + ); } #[test] @@ -853,8 +918,11 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; - vote_state.process_vote(&vote, &slot_hashes, 0); - assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), false); + assert_eq!(vote_state.process_vote(&vote, &slot_hashes, 0), Ok(())); + assert_eq!( + vote_state.check_slots_are_valid(&vote, &slot_hashes), + Err(VoteError::VoteTooOld) + ); } #[test] @@ -863,11 +931,14 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; - vote_state.process_vote(&vote, &slot_hashes, 0); + assert_eq!(vote_state.process_vote(&vote, &slot_hashes, 0), Ok(())); let vote = Vote::new(vec![0, 1], Hash::default()); let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)]; - assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), true); + assert_eq!( + vote_state.check_slots_are_valid(&vote, &slot_hashes), + Ok(()) + ); } #[test] @@ -876,11 +947,14 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; - vote_state.process_vote(&vote, &slot_hashes, 0); + assert_eq!(vote_state.process_vote(&vote, &slot_hashes, 0), Ok(())); let vote = Vote::new(vec![1], Hash::default()); let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)]; - assert_eq!(vote_state.check_slots_are_valid(&vote, &slot_hashes), true); + assert_eq!( + vote_state.check_slots_are_valid(&vote, &slot_hashes), + Ok(()) + ); } #[test] diff --git a/sdk/src/instruction_processor_utils.rs b/sdk/src/instruction_processor_utils.rs index 6035103e1b..853d7b8e06 100644 --- a/sdk/src/instruction_processor_utils.rs +++ b/sdk/src/instruction_processor_utils.rs @@ -1,7 +1,7 @@ use crate::account::KeyedAccount; use crate::instruction::InstructionError; use crate::pubkey::Pubkey; -use num_traits::FromPrimitive; +use num_traits::{FromPrimitive, ToPrimitive}; // All native programs export a symbol named process() pub const ENTRYPOINT: &str = "process"; @@ -29,14 +29,23 @@ macro_rules! solana_entrypoint( ) ); +impl From for InstructionError +where + T: ToPrimitive, +{ + fn from(error: T) -> Self { + InstructionError::CustomError(error.to_u32().unwrap_or(0xbad_c0de)) + } +} + pub trait DecodeError { - fn decode_custom_error_to_enum(int: u32) -> Option + fn decode_custom_error_to_enum(custom: u32) -> Option where E: FromPrimitive, { - E::from_u32(int) + E::from_u32(custom) } - fn type_of(&self) -> &'static str; + fn type_of() -> &'static str; } #[cfg(test)] @@ -53,7 +62,7 @@ mod tests { C, } impl DecodeError for TestEnum { - fn type_of(&self) -> &'static str { + fn type_of() -> &'static str { "TestEnum" } } diff --git a/sdk/src/system_instruction.rs b/sdk/src/system_instruction.rs index 40cc3dd991..0a5151d1fa 100644 --- a/sdk/src/system_instruction.rs +++ b/sdk/src/system_instruction.rs @@ -14,7 +14,7 @@ pub enum SystemError { } impl DecodeError for SystemError { - fn type_of(&self) -> &'static str { + fn type_of() -> &'static str { "SystemError" } }