switchboard_program/
lib.rs

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