buffett-core 0.1.1

Core library for Bitconch:buffett
Documentation
use bincode::{self, deserialize, serialize_into, serialized_size};
use buffett_budget::budget::Budget;
use buffett_budget::instruction::Instruction;
use chrono::prelude::{DateTime, Utc};
use buffett_budget::seal::Seal;
use buffett_interface::account::Account;
use buffett_interface::pubkey::Pubkey;
use std::io;
use crate::transaction::Transaction;

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum BudgetError {
    InsufficientFunds(Pubkey),
    ContractAlreadyExists(Pubkey),
    ContractNotPending(Pubkey),
    SourceIsPendingContract(Pubkey),
    UninitializedContract(Pubkey),
    NegativeTokens,
    DestinationMissing(Pubkey),
    FailedWitness,
    UserdataTooSmall,
    UserdataDeserializeFailure,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
pub struct BudgetState {
    pub initialized: bool,
    pub pending_budget: Option<Budget>,
}

pub const BUDGET_PROGRAM_ID: [u8; 32] = [
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
impl BudgetState {
    fn is_pending(&self) -> bool {
        self.pending_budget != None
    }
    pub fn id() -> Pubkey {
        Pubkey::new(&BUDGET_PROGRAM_ID)
    }
    pub fn check_id(program_id: &Pubkey) -> bool {
        program_id.as_ref() == BUDGET_PROGRAM_ID
    }

    fn apply_signature(
        &mut self,
        keys: &[Pubkey],
        account: &mut [Account],
    ) -> Result<(), BudgetError> {
        let mut final_payment = None;
        if let Some(ref mut budget) = self.pending_budget {
            budget.apply_seal(&Seal::Signature, &keys[0]);
            final_payment = budget.final_payment();
        }

        if let Some(payment) = final_payment {
            if keys.len() < 2 || payment.to != keys[2] {
                trace!("destination missing");
                return Err(BudgetError::DestinationMissing(payment.to));
            }
            self.pending_budget = None;
            account[1].tokens -= payment.balance;
            account[2].tokens += payment.balance;
        }
        Ok(())
    }

    fn apply_timestamp(
        &mut self,
        keys: &[Pubkey],
        accounts: &mut [Account],
        dt: DateTime<Utc>,
    ) -> Result<(), BudgetError> {
        let mut final_payment = None;

        if let Some(ref mut budget) = self.pending_budget {
            budget.apply_seal(&Seal::Timestamp(dt), &keys[0]);
            final_payment = budget.final_payment();
        }

        if let Some(payment) = final_payment {
            if keys.len() < 2 || payment.to != keys[2] {
                trace!("destination missing");
                return Err(BudgetError::DestinationMissing(payment.to));
            }
            self.pending_budget = None;
            accounts[1].tokens -= payment.balance;
            accounts[2].tokens += payment.balance;
        }
        Ok(())
    }

    fn apply_debits_to_budget_state(
        tx: &Transaction,
        accounts: &mut [Account],
        instruction: &Instruction,
    ) -> Result<(), BudgetError> {
        {
            if !accounts[0].userdata.is_empty() {
                trace!("source is pending");
                return Err(BudgetError::SourceIsPendingContract(tx.keys[0]));
            }
            if let Instruction::NewContract(contract) = &instruction {
                if contract.tokens < 0 {
                    trace!("negative tokens");
                    return Err(BudgetError::NegativeTokens);
                }

                if accounts[0].tokens < contract.tokens {
                    trace!("insufficient funds");
                    return Err(BudgetError::InsufficientFunds(tx.keys[0]));
                } else {
                    accounts[0].tokens -= contract.tokens;
                }
            };
        }
        Ok(())
    }

    
    fn apply_credits_to_budget_state(
        tx: &Transaction,
        accounts: &mut [Account],
        instruction: &Instruction,
    ) -> Result<(), BudgetError> {
        match instruction {
            Instruction::NewContract(contract) => {
                let budget = contract.budget.clone();
                if let Some(payment) = budget.final_payment() {
                    accounts[1].tokens += payment.balance;
                    Ok(())
                } else {
                    let existing = Self::deserialize(&accounts[1].userdata).ok();
                    if Some(true) == existing.map(|x| x.initialized) {
                        trace!("contract already exists");
                        Err(BudgetError::ContractAlreadyExists(tx.keys[1]))
                    } else {
                        let mut state = BudgetState::default();
                        state.pending_budget = Some(budget);
                        accounts[1].tokens += contract.tokens;
                        state.initialized = true;
                        state.serialize(&mut accounts[1].userdata)
                    }
                }
            }
            Instruction::ApplyDatetime(dt) => {
                if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) {
                    if !state.is_pending() {
                        Err(BudgetError::ContractNotPending(tx.keys[1]))
                    } else if !state.initialized {
                        trace!("contract is uninitialized");
                        Err(BudgetError::UninitializedContract(tx.keys[1]))
                    } else {
                        trace!("apply timestamp");
                        state.apply_timestamp(&tx.keys, accounts, *dt)?;
                        trace!("apply timestamp committed");
                        state.serialize(&mut accounts[1].userdata)
                    }
                } else {
                    Err(BudgetError::UninitializedContract(tx.keys[1]))
                }
            }
            Instruction::ApplySignature => {
                if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) {
                    if !state.is_pending() {
                        Err(BudgetError::ContractNotPending(tx.keys[1]))
                    } else if !state.initialized {
                        trace!("contract is uninitialized");
                        Err(BudgetError::UninitializedContract(tx.keys[1]))
                    } else {
                        trace!("apply signature");
                        state.apply_signature(&tx.keys, accounts)?;
                        trace!("apply signature committed");
                        state.serialize(&mut accounts[1].userdata)
                    }
                } else {
                    Err(BudgetError::UninitializedContract(tx.keys[1]))
                }
            }
            Instruction::NewVote(_vote) => {
                
                trace!("GOT VOTE! last_id={}", tx.last_id);
                Ok(())
            }
        }
    }
    fn serialize(&self, output: &mut [u8]) -> Result<(), BudgetError> {
        let len = serialized_size(self).unwrap() as u64;
        if output.len() < len as usize {
            warn!(
                "{} bytes required to serialize, only have {} bytes",
                len,
                output.len()
            );
            return Err(BudgetError::UserdataTooSmall);
        }
        {
            let writer = io::BufWriter::new(&mut output[..8]);
            serialize_into(writer, &len).unwrap();
        }

        {
            let writer = io::BufWriter::new(&mut output[8..8 + len as usize]);
            serialize_into(writer, self).unwrap();
        }
        Ok(())
    }

    pub fn deserialize(input: &[u8]) -> bincode::Result<Self> {
        if input.len() < 8 {
            return Err(Box::new(bincode::ErrorKind::SizeLimit));
        }
        let len: u64 = deserialize(&input[..8]).unwrap();
        if len < 2 {
            return Err(Box::new(bincode::ErrorKind::SizeLimit));
        }
        if input.len() < 8 + len as usize {
            return Err(Box::new(bincode::ErrorKind::SizeLimit));
        }
        deserialize(&input[8..8 + len as usize])
    }

    pub fn process_transaction(
        tx: &Transaction,
        accounts: &mut [Account],
    ) -> Result<(), BudgetError> {
        if let Ok(instruction) = deserialize(&tx.userdata) {
            trace!("process_transaction: {:?}", instruction);
            Self::apply_debits_to_budget_state(tx, accounts, &instruction)
                .and_then(|_| Self::apply_credits_to_budget_state(tx, accounts, &instruction))
        } else {
            info!("transaction instructions invalid on : {:?}", tx.userdata);
            Err(BudgetError::UserdataDeserializeFailure)
        }
    }

    pub fn get_balance(account: &Account) -> i64 {
        if let Ok(state) = deserialize(&account.userdata) {
            let state: BudgetState = state;
            if state.is_pending() {
                0
            } else {
                account.tokens
            }
        } else {
            account.tokens
        }
    }
}