diff --git a/Cargo.lock b/Cargo.lock index a9f071e5be..4a2bb9cd8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2755,7 +2755,6 @@ dependencies = [ "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "solana-logger 0.15.0", "solana-metrics 0.15.0", - "solana-runtime 0.15.0", "solana-sdk 0.15.0", ] diff --git a/programs/vote_api/Cargo.toml b/programs/vote_api/Cargo.toml index f30c2e159d..db0175b12b 100644 --- a/programs/vote_api/Cargo.toml +++ b/programs/vote_api/Cargo.toml @@ -17,9 +17,6 @@ solana-logger = { path = "../../logger", version = "0.15.0" } solana-metrics = { path = "../../metrics", version = "0.15.0" } solana-sdk = { path = "../../sdk", version = "0.15.0" } -[dev-dependencies] -solana-runtime = { path = "../../runtime", version = "0.15.0" } - [lib] name = "solana_vote_api" crate-type = ["lib"] diff --git a/programs/vote_api/src/lib.rs b/programs/vote_api/src/lib.rs index 3a9302a6b9..f59ec5a5b1 100644 --- a/programs/vote_api/src/lib.rs +++ b/programs/vote_api/src/lib.rs @@ -1,17 +1,9 @@ pub mod vote_instruction; pub mod vote_state; -use solana_sdk::pubkey::Pubkey; - const VOTE_PROGRAM_ID: [u8; 32] = [ 7, 97, 72, 29, 53, 116, 116, 187, 124, 77, 118, 36, 235, 211, 189, 179, 216, 53, 94, 115, 209, 16, 67, 252, 13, 163, 83, 128, 0, 0, 0, 0, ]; -pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == VOTE_PROGRAM_ID -} - -pub fn id() -> Pubkey { - Pubkey::new(&VOTE_PROGRAM_ID) -} +solana_sdk::solana_program_id!(VOTE_PROGRAM_ID); diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index be913deab5..3c5e43058d 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -71,10 +71,14 @@ pub fn process_instruction( trace!("process_instruction: {:?}", data); trace!("keyed_accounts: {:?}", keyed_accounts); + if keyed_accounts.is_empty() { + Err(InstructionError::InvalidInstructionData)?; + } + match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { VoteInstruction::InitializeAccount(node_id, commission) => { - let mut vote_account = &mut keyed_accounts[0]; - vote_state::initialize_account(&mut vote_account, &node_id, commission) + let vote_account = &mut keyed_accounts[0]; + vote_state::initialize_account(vote_account, &node_id, commission) } VoteInstruction::AuthorizeVoter(voter_id) => { let (vote_account, other_signers) = keyed_accounts.split_at_mut(1); @@ -82,7 +86,7 @@ pub fn process_instruction( vote_state::authorize_voter(vote_account, other_signers, &voter_id) } - VoteInstruction::Vote(vote) => { + VoteInstruction::Vote(votes) => { solana_metrics::submit( solana_metrics::influxdb::Point::new("vote-native") .add_field("count", solana_metrics::influxdb::Value::Integer(1)) @@ -91,7 +95,7 @@ pub fn process_instruction( let (vote_account, other_signers) = keyed_accounts.split_at_mut(1); let vote_account = &mut vote_account[0]; - vote_state::process_vote(vote_account, other_signers, &vote) + vote_state::process_votes(vote_account, other_signers, &votes) } } } @@ -99,121 +103,59 @@ pub fn process_instruction( #[cfg(test)] mod tests { use super::*; - use crate::id; - use crate::vote_instruction; - use crate::vote_state::{Vote, VoteState}; - use solana_runtime::bank::Bank; - use solana_runtime::bank_client::BankClient; - use solana_sdk::client::SyncClient; - use solana_sdk::genesis_block::GenesisBlock; - use solana_sdk::instruction::InstructionError; - use solana_sdk::message::Message; - use solana_sdk::pubkey::Pubkey; - use solana_sdk::signature::{Keypair, KeypairUtil}; - use solana_sdk::system_instruction; - use solana_sdk::transaction::{Result, TransactionError}; - - fn create_bank(lamports: u64) -> (Bank, Keypair) { - let (genesis_block, mint_keypair) = GenesisBlock::new(lamports); - let mut bank = Bank::new(&genesis_block); - bank.add_instruction_processor(id(), process_instruction); - (bank, mint_keypair) - } - - fn create_vote_account( - bank_client: &BankClient, - from_keypair: &Keypair, - vote_id: &Pubkey, - lamports: u64, - ) -> Result<()> { - let ixs = vote_instruction::create_account( - &from_keypair.pubkey(), - vote_id, - &Pubkey::new_rand(), - 0, - lamports, - ); - let message = Message::new(ixs); - bank_client - .send_message(&[from_keypair], message) - .map_err(|err| err.unwrap())?; - Ok(()) - } - - fn submit_vote( - bank_client: &BankClient, - vote_keypair: &Keypair, - tick_height: u64, - ) -> Result<()> { - let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), vec![Vote::new(tick_height)]); - bank_client - .send_instruction(vote_keypair, vote_ix) - .map_err(|err| err.unwrap())?; - Ok(()) - } + use solana_sdk::account::Account; + // these are for 100% coverage #[test] - fn test_vote_bank_basic() { - let (bank, from_keypair) = create_bank(10_000); - let bank_client = BankClient::new(bank); - - let vote_keypair = Keypair::new(); - let vote_id = vote_keypair.pubkey(); - - create_vote_account(&bank_client, &from_keypair, &vote_id, 100).unwrap(); - submit_vote(&bank_client, &vote_keypair, 0).unwrap(); - - let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap(); - let vote_state = VoteState::deserialize(&vote_account_data).unwrap(); - assert_eq!(vote_state.votes.len(), 1); - } - - #[test] - fn test_vote_via_bank_authorize_voter() { - let (bank, mallory_keypair) = create_bank(10_000); - let bank_client = BankClient::new(bank); - - let vote_keypair = Keypair::new(); - let vote_id = vote_keypair.pubkey(); - - create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap(); - - let mallory_id = mallory_keypair.pubkey(); - let vote_ix = vote_instruction::authorize_voter(&vote_id, &mallory_id); - - let message = Message::new(vec![vote_ix]); - assert!(bank_client.send_message(&[&vote_keypair], message).is_ok()); - } - - #[test] - fn test_vote_via_bank_with_no_signature() { - let (bank, mallory_keypair) = create_bank(10_000); - let bank_client = BankClient::new(bank); - - let vote_keypair = Keypair::new(); - let vote_id = vote_keypair.pubkey(); - - create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap(); - - let mallory_id = mallory_keypair.pubkey(); - let mut vote_ix = vote_instruction::vote(&vote_id, vec![Vote::new(0)]); - vote_ix.accounts[0].is_signer = false; // <--- attack!! No signer required. - - // Sneak in an instruction so that the transaction is signed but - // the 0th account in the second instruction is not! The program - // needs to check that it's signed. - let transfer_ix = system_instruction::transfer(&mallory_id, &vote_id, 1); - let message = Message::new(vec![transfer_ix, vote_ix]); - let result = bank_client.send_message(&[&mallory_keypair], message); - - // And ensure there's no vote. - let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap(); - let vote_state = VoteState::deserialize(&vote_account_data).unwrap(); - assert_eq!(vote_state.votes.len(), 0); - + fn test_vote_process_instruction_decode_bail() { assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError(1, InstructionError::MissingRequiredSignature) + super::process_instruction(&Pubkey::default(), &mut [], &[], 0,), + Err(InstructionError::InvalidInstructionData), ); } + + fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> { + let mut accounts = vec![]; + for _ in 0..instruction.accounts.len() { + accounts.push(Account::default()); + } + { + let mut keyed_accounts: Vec<_> = instruction + .accounts + .iter() + .zip(accounts.iter_mut()) + .map(|(meta, account)| KeyedAccount::new(&meta.pubkey, meta.is_signer, account)) + .collect(); + super::process_instruction( + &Pubkey::default(), + &mut keyed_accounts, + &instruction.data, + 0, + ) + } + } + + #[test] + fn test_vote_process_instruction() { + let instructions = create_account( + &Pubkey::default(), + &Pubkey::default(), + &Pubkey::default(), + 0, + 100, + ); + assert_eq!( + process_instruction(&instructions[1]), + Err(InstructionError::InvalidAccountData), + ); + assert_eq!( + process_instruction(&vote(&Pubkey::default(), vec![Vote::default()])), + Err(InstructionError::InvalidAccountData), + ); + assert_eq!( + process_instruction(&authorize_voter(&Pubkey::default(), &Pubkey::default())), + Err(InstructionError::InvalidAccountData), + ); + } + } diff --git a/programs/vote_api/src/vote_state.rs b/programs/vote_api/src/vote_state.rs index 528a049a29..0683bcbce2 100644 --- a/programs/vote_api/src/vote_state.rs +++ b/programs/vote_api/src/vote_state.rs @@ -102,10 +102,10 @@ impl VoteState { }) } - /// returns commission split as (voter_portion, staker_portion) tuple + /// returns commission split as (voter_portion, staker_portion, was_split) tuple /// /// if commission calculation is 100% one way or other, - /// indicate with None for the 0% side + /// indicate with false for was_split pub fn commission_split(&self, on: f64) -> (f64, f64, bool) { match self.commission { 0 => (0.0, on, false), @@ -228,7 +228,7 @@ pub fn initialize_account( )) } -pub fn process_vote( +pub fn process_votes( vote_account: &mut KeyedAccount, other_signers: &[KeyedAccount], votes: &[Vote], @@ -277,7 +277,7 @@ pub fn vote( vote_account: &mut Account, vote: &Vote, ) -> Result { - process_vote( + process_votes( &mut KeyedAccount::new(vote_id, true, vote_account), &[], &[vote.clone()], @@ -355,7 +355,7 @@ mod tests { let vote = vec![Vote::new(1)]; // unsigned - let res = process_vote( + let res = process_votes( &mut KeyedAccount::new(&vote_id, false, &mut vote_account), &[], &vote, @@ -363,7 +363,7 @@ mod tests { assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); // unsigned - let res = process_vote( + let res = process_votes( &mut KeyedAccount::new(&vote_id, true, &mut vote_account), &[], &vote, @@ -399,7 +399,7 @@ mod tests { // not signed by authorized voter let vote = vec![Vote::new(2)]; - let res = process_vote( + let res = process_votes( &mut KeyedAccount::new(&vote_id, true, &mut vote_account), &[], &vote, @@ -408,7 +408,7 @@ mod tests { // signed by authorized voter let vote = vec![Vote::new(2)]; - let res = process_vote( + let res = process_votes( &mut KeyedAccount::new(&vote_id, false, &mut vote_account), &[KeyedAccount::new( &authorized_voter_id, @@ -607,4 +607,23 @@ mod tests { vote_state_b.process_votes(&votes); assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b)); } + + #[test] + fn test_vote_state_commission_split() { + let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + + assert_eq!(vote_state.commission_split(1.0), (0.0, 1.0, false)); + + let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), std::u32::MAX); + assert_eq!(vote_state.commission_split(1.0), (1.0, 0.0, false)); + + let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), std::u32::MAX / 2); + let (voter_portion, staker_portion, was_split) = vote_state.commission_split(10.0); + + assert_eq!( + (voter_portion.round(), staker_portion.round(), was_split), + (5.0, 5.0, true) + ); + } + } diff --git a/sdk/src/instruction_processor_utils.rs b/sdk/src/instruction_processor_utils.rs index c160efb339..d6a34c6f4f 100644 --- a/sdk/src/instruction_processor_utils.rs +++ b/sdk/src/instruction_processor_utils.rs @@ -32,6 +32,26 @@ macro_rules! solana_entrypoint( ) ); +#[macro_export] +macro_rules! solana_program_id( + ($program_id:ident) => ( + + pub fn check_id(program_id: &solana_sdk::pubkey::Pubkey) -> bool { + program_id.as_ref() == $program_id + } + + pub fn id() -> solana_sdk::pubkey::Pubkey { + solana_sdk::pubkey::Pubkey::new(&$program_id) + } + + #[cfg(test)] + #[test] + fn test_program_id() { + assert!(check_id(&id())); + } + ) +); + /// Conveinence trait to covert bincode errors to instruction errors. pub trait State { fn state(&self) -> Result;