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;
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)
}
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)
}
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, false, true, lamports, data, owner, false, 100, )
}
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());
}
}