switchboard_program_packed/
lib.rs

1use quick_protobuf::deserialize_from_slice;
2use solana_program::account_info::AccountInfo;
3use solana_program::program_error::ProgramError;
4
5pub use switchboard_utils_packed::{FastRoundResultAccountData, FastRoundResult, fast_parse_switchboard_result};
6pub use switchboard_protos::protos::aggregator_state::AggregatorState;
7pub use switchboard_protos::protos::aggregator_state::mod_AggregatorState;
8pub use switchboard_protos::protos::aggregator_state::RoundResult;
9pub use switchboard_protos::protos::switchboard_account_types::SwitchboardAccountType;
10use zerocopy::AsBytes;
11use switchboard_protos::protos::vrf::VrfAccountData;
12
13/// Returns whether the current open round is considered valid for usage.
14pub fn is_current_round_valid(aggregator: &AggregatorState) -> Result<bool, ProgramError> {
15    let maybe_round = aggregator.current_round_result.clone();
16    if maybe_round.is_none() {
17        return Ok(false);
18    }
19    let round = maybe_round.unwrap();
20    let configs = aggregator.configs.as_ref().ok_or(ProgramError::InvalidAccountData)?;
21    if round.num_success < configs.min_confirmations {
22        return Ok(false);
23    }
24    Ok(true)
25}
26
27/// Given a Switchboard data feed account, this method will parse the account state.
28///
29/// Returns a ProgramError if the AccountInfo is unable to be borrowed or the
30/// account is not initialized as an aggregator.
31pub fn get_aggregator(switchboard_feed: &AccountInfo) -> Result<AggregatorState, ProgramError> {
32    let state_buffer = switchboard_feed.try_borrow_data()?;
33    if state_buffer.len() == 0 || state_buffer[0] != SwitchboardAccountType::TYPE_AGGREGATOR as u8 {
34        return Err(ProgramError::InvalidAccountData);
35    }
36    let aggregator_state: AggregatorState =
37        deserialize_from_slice(&state_buffer[1..]).map_err(|_| ProgramError::InvalidAccountData)?;
38    Ok(aggregator_state)
39}
40
41/// Returns the most recent resolution round that is considered valid for the aggregator.
42pub fn get_aggregator_result(aggregator: &AggregatorState) -> Result<RoundResult, ProgramError> {
43    let mut maybe_round = aggregator.current_round_result.clone();
44    if !is_current_round_valid(&aggregator)? {
45        maybe_round = aggregator.last_round_result.clone();
46    }
47    maybe_round.ok_or(ProgramError::InvalidAccountData)
48}
49
50pub struct BundleAccount<'a> {
51    account_info: &'a AccountInfo<'a>
52}
53
54impl<'a> BundleAccount<'a> {
55    const BUFFER_SIZE: usize = 500;
56
57    pub fn new(account: &'a AccountInfo<'a>) -> Result<Self, ProgramError> {
58        let buf = account.try_borrow_data()?;
59        if buf.len() == 0 || buf[0] != SwitchboardAccountType::TYPE_BUNDLE as u8 {
60            return Err(ProgramError::InvalidAccountData);
61        }
62        Ok(Self{
63            account_info: account
64        })
65    }
66
67    pub fn get_idx(&self, idx: usize) -> Result<FastRoundResultAccountData, ProgramError> {
68        let offset: usize = 1 + (idx * Self::BUFFER_SIZE);
69        let term = offset + std::mem::size_of::<FastRoundResultAccountData>();
70        let buf = self.account_info.try_borrow_data()?;
71        if buf.len() < term {
72            return Err(ProgramError::InvalidArgument);
73        }
74        let mut res = FastRoundResultAccountData {
75            ..Default::default()
76        };
77        let recv = res.as_bytes_mut();
78        recv.copy_from_slice(&buf[offset..term]);
79        if res.result.round_open_slot == 0 {
80            return Err(ProgramError::InvalidArgument);
81        }
82        Ok(res)
83    }
84}
85
86
87
88pub struct VrfAccount<'a> {
89    account_info: &'a AccountInfo<'a>
90}
91
92impl<'a> VrfAccount<'a> {
93
94    pub fn new(account: &'a AccountInfo<'a>) -> Result<Self, ProgramError> {
95        let buf = account.try_borrow_data()?;
96        if buf.len() == 0 || buf[0] != SwitchboardAccountType::TYPE_VRF as u8 {
97            return Err(ProgramError::InvalidAccountData);
98        }
99        Ok(Self{
100            account_info: account
101        })
102    }
103
104    /// returns the current verified randomness value held in the account.
105    /// returns ProgramError if not randomness currently exists or
106    /// if the number of proof verificaitons is less than the reuired
107    /// minimum numner of verifications.
108    pub fn get_verified_randomness(&self) -> Result<Vec<u8>, ProgramError> {
109        let vrf_state: VrfAccountData =
110            deserialize_from_slice(&self.account_info.try_borrow_data()?[1..])
111                .map_err(|_| ProgramError::InvalidAccountData)?;
112        let value = vrf_state.value.ok_or(ProgramError::InvalidAccountData)?;
113        let min_confirmations = vrf_state.min_proof_confirmations
114            .ok_or(ProgramError::InvalidAccountData)?;
115        let num_confirmations = vrf_state.num_proof_confirmations
116            .ok_or(ProgramError::InvalidAccountData)?;
117        if num_confirmations < min_confirmations {
118            return Err(ProgramError::InvalidAccountData);
119        }
120        Ok(value)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    pub fn new_account_info<'a>(
129        owner: &'a Pubkey,
130        key: &'a Pubkey,
131        lamports: &'a mut u64,
132        data: &'a mut [u8],
133    ) -> AccountInfo<'a> {
134        AccountInfo::new(
135            key,      // key: &'a Pubkey,
136            false,    // is_signer: bool,
137            true,     // is_writable: bool,
138            lamports, // lamports: &'a mut u64,
139            data,     // data: &'a mut [u8],
140            owner,    // owner: &'a Pubkey,
141            false,    // executable: bool,
142            100,      // rent_epoch: Epoch
143        )
144    }
145
146    pub fn create_aggregator(current_round: RoundResult, last_round: RoundResult) -> AggregatorState {
147        AggregatorState {
148            version: Some(1),
149            configs: Some(mod_AggregatorState::Configs {
150                min_confirmations: Some(10),
151                min_update_delay_seconds: Some(10),
152                locked: Some(false),
153                schedule: None,
154            }),
155            fulfillment_manager_pubkey: Some(Vec::new()),
156            job_definition_pubkeys: Vec::new(),
157            agreement: None,
158            current_round_result: Some(current_round),
159            last_round_result: Some(last_round),
160            parse_optimized_result_address: None,
161            bundle_auth_addresses: Vec::new(),
162        }
163    }
164
165    #[test]
166    fn test_reject_current_on_sucess_count() {
167        let current_round = RoundResult {
168            num_success: Some(2),
169            num_error: Some(5),
170            result: Some(97.0),
171            round_open_slot: Some(1),
172            round_open_timestamp: Some(1),
173            min_response: Some(96.0),
174            max_response: Some(100.0),
175            medians: Vec::new(),
176        };
177        let last_round = RoundResult {
178            num_success: Some(30),
179            num_error: Some(0),
180            result: Some(100.0),
181            round_open_slot: Some(1),
182            round_open_timestamp: Some(1),
183            min_response: Some(100.0),
184            max_response: Some(100.0),
185            medians: Vec::new(),
186        };
187        let aggregator = create_aggregator(current_round.clone(), last_round.clone());
188        assert_eq!(get_aggregator_result(&aggregator).unwrap(), last_round.clone());
189    }
190
191    #[test]
192    fn test_accept_current_on_sucess_count() {
193        let current_round = RoundResult {
194            num_success: Some(20),
195            num_error: Some(5),
196            result: Some(97.0),
197            round_open_slot: Some(1),
198            round_open_timestamp: Some(1),
199            min_response: Some(96.0),
200            max_response: Some(100.0),
201            medians: Vec::new(),
202        };
203        let last_round = RoundResult {
204            num_success: Some(30),
205            num_error: Some(0),
206            result: Some(100.0),
207            round_open_slot: Some(1),
208            round_open_timestamp: Some(1),
209            min_response: Some(100.0),
210            max_response: Some(100.0),
211            medians: Vec::new(),
212        };
213        let aggregator = create_aggregator(current_round.clone(), last_round.clone());
214        assert_eq!(get_aggregator_result(&aggregator).unwrap(), current_round.clone());
215    }
216}