//! budget program use crate::{ budget_expr::Witness, budget_instruction::{BudgetError, BudgetInstruction}, budget_state::BudgetState, }; use chrono::prelude::{DateTime, Utc}; use log::*; use solana_sdk::{ account::KeyedAccount, hash::hash, instruction::InstructionError, program_utils::{limited_deserialize, next_keyed_account}, pubkey::Pubkey, }; /// Process a Witness Signature. Any payment plans waiting on this signature /// will progress one step. fn apply_signature( budget_state: &mut BudgetState, witness_keyed_account: &KeyedAccount, contract_keyed_account: &KeyedAccount, to_keyed_account: Result<&KeyedAccount, InstructionError>, ) -> Result<(), InstructionError> { let mut final_payment = None; if let Some(ref mut expr) = budget_state.pending_budget { let key = witness_keyed_account.signer_key().unwrap(); expr.apply_witness(&Witness::Signature, key); final_payment = expr.final_payment(); } if let Some(payment) = final_payment { if let Some(key) = witness_keyed_account.signer_key() { if &payment.to == key { budget_state.pending_budget = None; contract_keyed_account.try_account_ref_mut()?.lamports -= payment.lamports; witness_keyed_account.try_account_ref_mut()?.lamports += payment.lamports; return Ok(()); } } let to_keyed_account = to_keyed_account?; if &payment.to != to_keyed_account.unsigned_key() { trace!("destination missing"); return Err(BudgetError::DestinationMissing.into()); } budget_state.pending_budget = None; contract_keyed_account.try_account_ref_mut()?.lamports -= payment.lamports; to_keyed_account.try_account_ref_mut()?.lamports += payment.lamports; } Ok(()) } /// Process a Witness Timestamp. Any payment plans waiting on this timestamp /// will progress one step. fn apply_timestamp( budget_state: &mut BudgetState, witness_keyed_account: &KeyedAccount, contract_keyed_account: &KeyedAccount, to_keyed_account: Result<&KeyedAccount, InstructionError>, dt: DateTime, ) -> Result<(), InstructionError> { // Check to see if any timelocked transactions can be completed. let mut final_payment = None; if let Some(ref mut expr) = budget_state.pending_budget { let key = witness_keyed_account.signer_key().unwrap(); expr.apply_witness(&Witness::Timestamp(dt), key); final_payment = expr.final_payment(); } if let Some(payment) = final_payment { let to_keyed_account = to_keyed_account?; if &payment.to != to_keyed_account.unsigned_key() { trace!("destination missing"); return Err(BudgetError::DestinationMissing.into()); } budget_state.pending_budget = None; contract_keyed_account.try_account_ref_mut()?.lamports -= payment.lamports; to_keyed_account.try_account_ref_mut()?.lamports += payment.lamports; } Ok(()) } /// Process an AccountData Witness and any payment waiting on it. fn apply_account_data( budget_state: &mut BudgetState, witness_keyed_account: &KeyedAccount, contract_keyed_account: &KeyedAccount, to_keyed_account: Result<&KeyedAccount, InstructionError>, ) -> Result<(), InstructionError> { // Check to see if any timelocked transactions can be completed. let mut final_payment = None; if let Some(ref mut expr) = budget_state.pending_budget { let key = witness_keyed_account.unsigned_key(); let program_id = witness_keyed_account.owner()?; let actual_hash = hash(&witness_keyed_account.try_account_ref()?.data); expr.apply_witness(&Witness::AccountData(actual_hash, program_id), key); final_payment = expr.final_payment(); } if let Some(payment) = final_payment { let to_keyed_account = to_keyed_account?; if &payment.to != to_keyed_account.unsigned_key() { trace!("destination missing"); return Err(BudgetError::DestinationMissing.into()); } budget_state.pending_budget = None; contract_keyed_account.try_account_ref_mut()?.lamports -= payment.lamports; to_keyed_account.try_account_ref_mut()?.lamports += payment.lamports; } Ok(()) } pub fn process_instruction( _program_id: &Pubkey, keyed_accounts: &[KeyedAccount], data: &[u8], ) -> Result<(), InstructionError> { let keyed_accounts_iter = &mut keyed_accounts.iter(); let instruction = limited_deserialize(data)?; trace!("process_instruction: {:?}", instruction); match instruction { BudgetInstruction::InitializeAccount(expr) => { let contract_keyed_account = next_keyed_account(keyed_accounts_iter)?; if let Some(payment) = expr.final_payment() { let to_keyed_account = contract_keyed_account; let contract_keyed_account = next_keyed_account(keyed_accounts_iter)?; contract_keyed_account.try_account_ref_mut()?.lamports = 0; to_keyed_account.try_account_ref_mut()?.lamports += payment.lamports; return Ok(()); } let existing = BudgetState::deserialize(&contract_keyed_account.try_account_ref_mut()?.data).ok(); if Some(true) == existing.map(|x| x.initialized) { trace!("contract already exists"); return Err(InstructionError::AccountAlreadyInitialized); } let mut budget_state = BudgetState::default(); budget_state.pending_budget = Some(*expr); budget_state.initialized = true; budget_state.serialize(&mut contract_keyed_account.try_account_ref_mut()?.data) } BudgetInstruction::ApplyTimestamp(dt) => { let witness_keyed_account = next_keyed_account(keyed_accounts_iter)?; let contract_keyed_account = next_keyed_account(keyed_accounts_iter)?; let mut budget_state = BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data)?; if !budget_state.is_pending() { return Ok(()); // Nothing to do here. } if !budget_state.initialized { trace!("contract is uninitialized"); return Err(InstructionError::UninitializedAccount); } if witness_keyed_account.signer_key().is_none() { return Err(InstructionError::MissingRequiredSignature); } trace!("apply timestamp"); apply_timestamp( &mut budget_state, witness_keyed_account, contract_keyed_account, next_keyed_account(keyed_accounts_iter), dt, )?; trace!("apply timestamp committed"); budget_state.serialize(&mut contract_keyed_account.try_account_ref_mut()?.data) } BudgetInstruction::ApplySignature => { let witness_keyed_account = next_keyed_account(keyed_accounts_iter)?; let contract_keyed_account = next_keyed_account(keyed_accounts_iter)?; let mut budget_state = BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data)?; if !budget_state.is_pending() { return Ok(()); // Nothing to do here. } if !budget_state.initialized { trace!("contract is uninitialized"); return Err(InstructionError::UninitializedAccount); } if witness_keyed_account.signer_key().is_none() { return Err(InstructionError::MissingRequiredSignature); } trace!("apply signature"); apply_signature( &mut budget_state, witness_keyed_account, contract_keyed_account, next_keyed_account(keyed_accounts_iter), )?; trace!("apply signature committed"); budget_state.serialize(&mut contract_keyed_account.try_account_ref_mut()?.data) } BudgetInstruction::ApplyAccountData => { let witness_keyed_account = next_keyed_account(keyed_accounts_iter)?; let contract_keyed_account = next_keyed_account(keyed_accounts_iter)?; let mut budget_state = BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data)?; if !budget_state.is_pending() { return Ok(()); // Nothing to do here. } if !budget_state.initialized { trace!("contract is uninitialized"); return Err(InstructionError::UninitializedAccount); } apply_account_data( &mut budget_state, witness_keyed_account, contract_keyed_account, next_keyed_account(keyed_accounts_iter), )?; trace!("apply account data committed"); budget_state.serialize(&mut contract_keyed_account.try_account_ref_mut()?.data) } } } #[cfg(test)] mod tests { use super::*; use crate::budget_instruction; use crate::id; use solana_runtime::bank::Bank; use solana_runtime::bank_client::BankClient; use solana_sdk::account::Account; use solana_sdk::client::SyncClient; use solana_sdk::genesis_config::create_genesis_config; use solana_sdk::hash::hash; use solana_sdk::instruction::InstructionError; use solana_sdk::message::Message; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::transaction::TransactionError; fn create_bank(lamports: u64) -> (Bank, Keypair) { let (genesis_config, mint_keypair) = create_genesis_config(lamports); let mut bank = Bank::new(&genesis_config); bank.add_instruction_processor(id(), process_instruction); (bank, mint_keypair) } #[test] fn test_initialize_no_panic() { let (bank, alice_keypair) = create_bank(1); let bank_client = BankClient::new(bank); let alice_pubkey = alice_keypair.pubkey(); let budget_keypair = Keypair::new(); let budget_pubkey = budget_keypair.pubkey(); let bob_pubkey = Pubkey::new_rand(); let mut instructions = budget_instruction::payment(&alice_pubkey, &bob_pubkey, &budget_pubkey, 1); instructions[1].accounts = vec![]; //