diff --git a/programs/budget/src/budget_program.rs b/programs/budget/src/budget_program.rs index 00ec14b579..244959de16 100644 --- a/programs/budget/src/budget_program.rs +++ b/programs/budget/src/budget_program.rs @@ -1,11 +1,12 @@ //! budget program -use bincode::deserialize; +use bincode::{deserialize, serialize}; use chrono::prelude::{DateTime, Utc}; use log::*; use solana_budget_api::budget_instruction::BudgetInstruction; use solana_budget_api::budget_state::{BudgetError, BudgetState}; use solana_budget_api::payment_plan::Witness; use solana_sdk::account::KeyedAccount; +use solana_sdk::native_program::ProgramError; use solana_sdk::pubkey::Pubkey; /// Process a Witness Signature. Any payment plans waiting on this signature @@ -16,10 +17,7 @@ fn apply_signature( ) -> Result<(), BudgetError> { let mut final_payment = None; if let Some(ref mut expr) = budget_state.pending_budget { - let key = match keyed_accounts[0].signer_key() { - None => return Err(BudgetError::UnsignedKey), - Some(key) => key, - }; + let key = keyed_accounts[0].signer_key().unwrap(); expr.apply_witness(&Witness::Signature, key); final_payment = expr.final_payment(); } @@ -55,10 +53,7 @@ fn apply_timestamp( let mut final_payment = None; if let Some(ref mut expr) = budget_state.pending_budget { - let key = match keyed_accounts[0].signer_key() { - None => return Err(BudgetError::UnsignedKey), - Some(key) => key, - }; + let key = keyed_accounts[0].signer_key().unwrap(); expr.apply_witness(&Witness::Timestamp(dt), key); final_payment = expr.final_payment(); } @@ -75,83 +70,75 @@ fn apply_timestamp( Ok(()) } -fn apply_debits( +pub fn process_instruction( + _program_id: &Pubkey, keyed_accounts: &mut [KeyedAccount], - instruction: &BudgetInstruction, -) -> Result<(), BudgetError> { + data: &[u8], +) -> Result<(), ProgramError> { + let instruction = deserialize(data).map_err(|err| { + info!("Invalid transaction data: {:?} {:?}", data, err); + ProgramError::InvalidInstructionData + })?; + + trace!("process_instruction: {:?}", instruction); + match instruction { BudgetInstruction::InitializeAccount(expr) => { let expr = expr.clone(); if let Some(payment) = expr.final_payment() { keyed_accounts[1].account.lamports = 0; keyed_accounts[0].account.lamports += payment.lamports; - Ok(()) - } else { - let existing = BudgetState::deserialize(&keyed_accounts[0].account.data).ok(); - if Some(true) == existing.map(|x| x.initialized) { - trace!("contract already exists"); - Err(BudgetError::ContractAlreadyExists) - } else { - let mut budget_state = BudgetState::default(); - budget_state.pending_budget = Some(expr); - budget_state.initialized = true; - budget_state.serialize(&mut keyed_accounts[0].account.data) - } + return Ok(()); } + let existing = BudgetState::deserialize(&keyed_accounts[0].account.data).ok(); + if Some(true) == existing.map(|x| x.initialized) { + trace!("contract already exists"); + return Err(ProgramError::AccountAlreadyInitialized); + } + let mut budget_state = BudgetState::default(); + budget_state.pending_budget = Some(expr); + budget_state.initialized = true; + budget_state.serialize(&mut keyed_accounts[0].account.data) } BudgetInstruction::ApplyTimestamp(dt) => { - if let Ok(mut budget_state) = BudgetState::deserialize(&keyed_accounts[1].account.data) - { - if !budget_state.is_pending() { - Err(BudgetError::ContractNotPending) - } else if !budget_state.initialized { - trace!("contract is uninitialized"); - Err(BudgetError::UninitializedContract) - } else { - trace!("apply timestamp"); - apply_timestamp(&mut budget_state, keyed_accounts, *dt)?; - trace!("apply timestamp committed"); - budget_state.serialize(&mut keyed_accounts[1].account.data) - } - } else { - Err(BudgetError::UninitializedContract) + let mut budget_state = BudgetState::deserialize(&keyed_accounts[1].account.data)?; + if !budget_state.is_pending() { + return Ok(()); // Nothing to do here. } + if !budget_state.initialized { + trace!("contract is uninitialized"); + return Err(ProgramError::UninitializedAccount); + } + if keyed_accounts[0].signer_key().is_none() { + return Err(ProgramError::MissingRequiredSignature); + } + trace!("apply timestamp"); + apply_timestamp(&mut budget_state, keyed_accounts, dt) + .map_err(|e| ProgramError::CustomError(serialize(&e).unwrap()))?; + trace!("apply timestamp committed"); + budget_state.serialize(&mut keyed_accounts[1].account.data) } BudgetInstruction::ApplySignature => { - if let Ok(mut budget_state) = BudgetState::deserialize(&keyed_accounts[1].account.data) - { - if !budget_state.is_pending() { - Err(BudgetError::ContractNotPending) - } else if !budget_state.initialized { - trace!("contract is uninitialized"); - Err(BudgetError::UninitializedContract) - } else { - trace!("apply signature"); - apply_signature(&mut budget_state, keyed_accounts)?; - trace!("apply signature committed"); - budget_state.serialize(&mut keyed_accounts[1].account.data) - } - } else { - Err(BudgetError::UninitializedContract) + let mut budget_state = BudgetState::deserialize(&keyed_accounts[1].account.data)?; + if !budget_state.is_pending() { + return Ok(()); // Nothing to do here. } + if !budget_state.initialized { + trace!("contract is uninitialized"); + return Err(ProgramError::UninitializedAccount); + } + if keyed_accounts[0].signer_key().is_none() { + return Err(ProgramError::MissingRequiredSignature); + } + trace!("apply signature"); + apply_signature(&mut budget_state, keyed_accounts) + .map_err(|e| ProgramError::CustomError(serialize(&e).unwrap()))?; + trace!("apply signature committed"); + budget_state.serialize(&mut keyed_accounts[1].account.data) } } } -pub fn process_instruction( - _program_id: &Pubkey, - keyed_accounts: &mut [KeyedAccount], - data: &[u8], -) -> Result<(), BudgetError> { - let instruction = deserialize(data).map_err(|err| { - info!("Invalid transaction data: {:?} {:?}", data, err); - BudgetError::AccountDataDeserializeFailure - })?; - - trace!("process_instruction: {:?}", instruction); - apply_debits(keyed_accounts, &instruction) -} - #[cfg(test)] mod test { use super::*; @@ -162,12 +149,12 @@ mod test { use solana_sdk::hash::Hash; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_program; - use solana_sdk::transaction::Transaction; + use solana_sdk::transaction::{InstructionError, Transaction, TransactionError}; fn process_transaction( tx: &Transaction, tx_accounts: &mut Vec, - ) -> Result<(), BudgetError> { + ) -> Result<(), TransactionError> { runtime::process_transaction(tx, tx_accounts, process_instruction) } @@ -219,7 +206,10 @@ mod test { // Ensure the transaction fails because of the unsigned key. assert_eq!( process_transaction(&tx, &mut accounts), - Err(BudgetError::UnsignedKey) + Err(TransactionError::InstructionError( + 0, + InstructionError::ProgramError(ProgramError::MissingRequiredSignature) + )) ); } @@ -255,7 +245,10 @@ mod test { // Ensure the transaction fails because of the unsigned key. assert_eq!( process_transaction(&tx, &mut accounts), - Err(BudgetError::UnsignedKey) + Err(TransactionError::InstructionError( + 0, + InstructionError::ProgramError(ProgramError::MissingRequiredSignature) + )) ); } @@ -295,8 +288,13 @@ mod test { Hash::default(), ); assert_eq!( - process_transaction(&tx, &mut accounts), - Err(BudgetError::DestinationMissing) + process_transaction(&tx, &mut accounts).unwrap_err(), + TransactionError::InstructionError( + 0, + InstructionError::ProgramError(ProgramError::CustomError( + serialize(&BudgetError::DestinationMissing).unwrap() + )) + ) ); assert_eq!(accounts[from_account].lamports, 0); assert_eq!(accounts[contract_account].lamports, 1); @@ -323,10 +321,7 @@ mod test { assert!(!budget_state.is_pending()); // try to replay the timestamp contract - assert_eq!( - process_transaction(&tx, &mut accounts), - Err(BudgetError::ContractNotPending) - ); + process_transaction(&tx, &mut accounts).unwrap(); assert_eq!(accounts[from_account].lamports, 0); assert_eq!(accounts[contract_account].lamports, 0); assert_eq!(accounts[to_account].lamports, 1); @@ -391,10 +386,7 @@ mod test { &from.pubkey(), Hash::default(), ); - assert_eq!( - process_transaction(&tx, &mut accounts), - Err(BudgetError::ContractNotPending) - ); + process_transaction(&tx, &mut accounts).unwrap(); assert_eq!(accounts[from_account].lamports, 1); assert_eq!(accounts[contract_account].lamports, 0); assert_eq!(accounts.get(pay_account), None); diff --git a/programs/budget/src/lib.rs b/programs/budget/src/lib.rs index 81c824c3ed..5021ad3953 100644 --- a/programs/budget/src/lib.rs +++ b/programs/budget/src/lib.rs @@ -1,7 +1,6 @@ mod budget_program; use crate::budget_program::process_instruction; -use bincode::serialize; use log::*; use solana_sdk::account::KeyedAccount; use solana_sdk::native_program::ProgramError; @@ -20,5 +19,4 @@ fn entrypoint( trace!("process_instruction: {:?}", data); trace!("keyed_accounts: {:?}", keyed_accounts); process_instruction(program_id, keyed_accounts, data) - .map_err(|e| ProgramError::CustomError(serialize(&e).unwrap())) } diff --git a/programs/budget_api/src/budget_state.rs b/programs/budget_api/src/budget_state.rs index 22d07958be..0696eeab47 100644 --- a/programs/budget_api/src/budget_state.rs +++ b/programs/budget_api/src/budget_state.rs @@ -2,19 +2,11 @@ use crate::budget_expr::BudgetExpr; use bincode::{self, deserialize, serialize_into}; use serde_derive::{Deserialize, Serialize}; +use solana_sdk::native_program::ProgramError; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum BudgetError { - InsufficientFunds, - ContractAlreadyExists, - ContractNotPending, - SourceIsPendingContract, - UninitializedContract, DestinationMissing, - FailedWitness, - AccountDataTooSmall, - AccountDataDeserializeFailure, - UnsignedKey, } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] @@ -35,14 +27,12 @@ impl BudgetState { self.pending_budget.is_some() } - pub fn serialize(&self, output: &mut [u8]) -> Result<(), BudgetError> { - serialize_into(output, self).map_err(|err| match *err { - _ => BudgetError::AccountDataTooSmall, - }) + pub fn serialize(&self, output: &mut [u8]) -> Result<(), ProgramError> { + serialize_into(output, self).map_err(|_| ProgramError::AccountDataTooSmall) } - pub fn deserialize(input: &[u8]) -> bincode::Result { - deserialize(input) + pub fn deserialize(input: &[u8]) -> Result { + deserialize(input).map_err(|_| ProgramError::InvalidAccountData) } } @@ -67,7 +57,7 @@ mod test { let b = BudgetState::default(); assert_eq!( b.serialize(&mut a.data), - Err(BudgetError::AccountDataTooSmall) + Err(ProgramError::AccountDataTooSmall) ); } } diff --git a/runtime/src/runtime.rs b/runtime/src/runtime.rs index 527c7b117e..9df9e8edb0 100644 --- a/runtime/src/runtime.rs +++ b/runtime/src/runtime.rs @@ -189,19 +189,20 @@ pub fn execute_transaction( /// A utility function for unit-tests. Same as execute_transaction(), but bypasses the loaders /// for easier usage and better stack traces. -pub fn process_transaction( +pub fn process_transaction( tx: &Transaction, tx_accounts: &mut Vec, process_instruction: F, -) -> Result<(), E> +) -> Result<(), TransactionError> where - F: Fn(&Pubkey, &mut [KeyedAccount], &[u8]) -> Result<(), E>, + F: Fn(&Pubkey, &mut [KeyedAccount], &[u8]) -> Result<(), ProgramError>, { for _ in tx_accounts.len()..tx.account_keys.len() { tx_accounts.push(Account::new(0, 0, &system_program::id())); } for (i, ix) in tx.instructions.iter().enumerate() { - let mut ix_accounts = get_subset_unchecked_mut(tx_accounts, &ix.accounts).unwrap(); + let mut ix_accounts = get_subset_unchecked_mut(tx_accounts, &ix.accounts) + .map_err(|err| TransactionError::InstructionError(i as u8, err))?; let mut keyed_accounts: Vec<_> = ix .accounts .iter() @@ -215,12 +216,14 @@ where .collect(); let program_id = tx.program_id(i); - if system_program::check_id(&program_id) { + let result = if system_program::check_id(&program_id) { crate::system_program::entrypoint(&program_id, &mut keyed_accounts, &ix.data, 0) - .unwrap(); } else { - process_instruction(&program_id, &mut keyed_accounts, &ix.data)?; - } + process_instruction(&program_id, &mut keyed_accounts, &ix.data) + }; + result.map_err(|err| { + TransactionError::InstructionError(i as u8, InstructionError::ProgramError(err)) + })?; } Ok(()) } diff --git a/sdk/src/native_program.rs b/sdk/src/native_program.rs index 147ff7d2fe..83dde1bbba 100644 --- a/sdk/src/native_program.rs +++ b/sdk/src/native_program.rs @@ -26,6 +26,12 @@ pub enum ProgramError { /// A signature was required but not found MissingRequiredSignature, + /// An initialize instruction was sent to an account that has already been initialized. + AccountAlreadyInitialized, + + /// An attempt to operate on an account that hasn't been initialized. + UninitializedAccount, + /// CustomError allows on-chain programs to implement program-specific error types and see /// them returned by the Solana runtime. A CustomError may be any type that is serialized /// to a Vec of bytes, max length 32 bytes. Any CustomError Vec greater than this length will