switchboard_program/
lib.rs1use 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
14pub 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
28pub 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
42pub 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 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, false, true, lamports, data, owner, false, 100, )
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}