switchboard-program 0.1.24

A Rust library to interact with Switchboard's hosted data feeds.
Documentation
use quick_protobuf::deserialize_from_slice;
use solana_program::account_info::AccountInfo;
use solana_program::program_error::ProgramError;
use switchboard_protos::protos::aggregator_state::mod_AggregatorState;
use solana_program::pubkey::Pubkey;

pub use switchboard_protos::protos::aggregator_state::AggregatorState;
pub use switchboard_protos::protos::aggregator_state::RoundResult;

/// Returns whether the current open round is considered valid for usage.
pub fn is_current_round_valid(aggregator: &AggregatorState) -> Result<bool, ProgramError> {
    let maybe_round = aggregator.current_round_result.clone();
    if maybe_round.is_none() {
        return Ok(false);
    }
    let round = maybe_round.unwrap();
    let configs = aggregator.configs.as_ref().ok_or(ProgramError::InvalidAccountData)?;
    if round.num_success < configs.min_confirmations {
        return Ok(false);
    }
    Ok(true)
}

/// Given a Switchboard data feed account, this method will parse the account state.
///
/// Returns a ProgramError if the AccountInfo is unable to be borrowed or the
/// account is not initialized as an aggregator.
pub fn get_aggregator<'a>(switchboard_feed: &'a AccountInfo<'a>) -> Result<AggregatorState, ProgramError> {
    let state_buffer = switchboard_feed.try_borrow_data()?;
    let aggregator_state: AggregatorState =
        deserialize_from_slice(&state_buffer[1..]).map_err(|_| ProgramError::InvalidAccountData)?;
    Ok(aggregator_state)
}

/// Returns the most recent resolution round that is considered valid for the aggregator.
pub fn get_aggregator_result<'a>(aggregator: &AggregatorState) -> Result<RoundResult, ProgramError> {
    let mut maybe_round = aggregator.current_round_result.clone();
    if !is_current_round_valid(&aggregator)? {
        maybe_round = aggregator.last_round_result.clone();
    }
    maybe_round.ok_or(ProgramError::InvalidAccountData)
}

#[cfg(test)]
mod tests {
    use super::*;

    pub fn new_account_info<'a>(
        owner: &'a Pubkey,
        key: &'a Pubkey,
        lamports: &'a mut u64,
        data: &'a mut [u8],
    ) -> AccountInfo<'a> {
        AccountInfo::new(
            key,      // key: &'a Pubkey,
            false,    // is_signer: bool,
            true,     // is_writable: bool,
            lamports, // lamports: &'a mut u64,
            data,     // data: &'a mut [u8],
            owner,    // owner: &'a Pubkey,
            false,    // executable: bool,
            100,      // rent_epoch: Epoch
        )
    }

    pub fn create_aggregator(current_round: RoundResult, last_round: RoundResult) -> AggregatorState {
        AggregatorState {
            version: Some(1),
            configs: Some(mod_AggregatorState::Configs {
                min_confirmations: Some(10),
                min_update_delay_seconds: Some(10),
                locked: Some(false),
            }),
            fulfillment_manager_pubkey: Some(Vec::new()),
            job_definition_pubkeys: Vec::new(),
            agreement: None,
            current_round_result: Some(current_round),
            last_round_result: Some(last_round),
        }
    }

    #[test]
    fn test_reject_current_on_sucess_count() {
        let current_round = RoundResult {
            num_success: Some(2),
            num_error: Some(5),
            result: Some(97.0),
            round_open_slot: Some(1),
            round_open_timestamp: Some(1),
            min_response: Some(96.0),
            max_response: Some(100.0),
            medians: Vec::new(),
        };
        let last_round = RoundResult {
            num_success: Some(30),
            num_error: Some(0),
            result: Some(100.0),
            round_open_slot: Some(1),
            round_open_timestamp: Some(1),
            min_response: Some(100.0),
            max_response: Some(100.0),
            medians: Vec::new(),
        };
        let aggregator = create_aggregator(current_round.clone(), last_round.clone());
        assert_eq!(get_aggregator_result(&aggregator).unwrap(), last_round.clone());
    }

    #[test]
    fn test_accept_current_on_sucess_count() {
        let current_round = RoundResult {
            num_success: Some(20),
            num_error: Some(5),
            result: Some(97.0),
            round_open_slot: Some(1),
            round_open_timestamp: Some(1),
            min_response: Some(96.0),
            max_response: Some(100.0),
            medians: Vec::new(),
        };
        let last_round = RoundResult {
            num_success: Some(30),
            num_error: Some(0),
            result: Some(100.0),
            round_open_slot: Some(1),
            round_open_timestamp: Some(1),
            min_response: Some(100.0),
            max_response: Some(100.0),
            medians: Vec::new(),
        };
        let aggregator = create_aggregator(current_round.clone(), last_round.clone());
        assert_eq!(get_aggregator_result(&aggregator).unwrap(), current_round.clone());
    }
}