switchboard_utils_bm/
lib.rs

1use quick_protobuf::message::MessageWrite;
2use quick_protobuf::writer::serialize_into_vec;
3use solana_program::pubkey::Pubkey;
4use solana_program::sysvar::Sysvar;
5use solana_program::sysvar::rent;
6use solana_program::{account_info::AccountInfo, msg, program_error::ProgramError};
7use std::clone::Clone;
8use switchboard_protos::protos::aggregator_state::AggregatorState;
9use switchboard_protos::protos::aggregator_state::mod_AggregatorState;
10use switchboard_protos::protos::fulfillment_manager::FulfillmentManagerState;
11use switchboard_protos::protos::fulfillment_manager::mod_FulfillmentManagerState;
12use switchboard_protos::protos::switchboard_account_types::SwitchboardAccountType;
13use std::io::Error;
14use std::io::Write;
15use rust_decimal::prelude::Decimal;
16use rust_decimal::prelude::FromPrimitive;
17use bytemuck::{bytes_of_mut, bytes_of};
18use bytemuck::{Pod, Zeroable};
19
20#[derive(Default,Copy,Clone,Debug)]
21#[repr(C)]
22pub struct SwitchboardDecimal {
23    pub mantissa: i128,
24    pub scale: u64
25}
26unsafe impl Pod for SwitchboardDecimal {}
27unsafe impl Zeroable for SwitchboardDecimal {}
28
29impl SwitchboardDecimal {
30    pub fn from_f64(input: f64) -> Option<Self> {
31        let dec = Decimal::from_f64(input);
32        if dec.is_none() {
33            return None;
34        }
35        let dec = dec.unwrap();
36        Some(Self {
37            mantissa: dec.mantissa(),
38            scale: dec.scale().into(),
39        })
40    }
41}
42
43pub type PubkeyBuffer = [u8; 32];
44
45#[derive(Default,Copy,Clone,Debug)]
46#[repr(C)]
47pub struct FastRoundResult {
48    pub num_success: i32,
49    pub num_error: i32,
50    // TODO: change name to 'value'
51    pub result: f64,
52    pub round_open_slot: u64,
53    pub round_open_timestamp: i64,
54    pub min_response: f64,
55    pub max_response: f64,
56    // Rust decimal representation of the current result.
57    pub decimal: SwitchboardDecimal,
58}
59unsafe impl Pod for FastRoundResult {}
60unsafe impl Zeroable for FastRoundResult {}
61
62#[derive(Default,Copy,Clone,Debug)]
63#[repr(C)]
64pub struct FastRoundResultAccountData {
65    pub parent: PubkeyBuffer,
66    // TODO: change name to 'round_result'
67    pub result: FastRoundResult,
68}
69unsafe impl Pod for FastRoundResultAccountData {}
70unsafe impl Zeroable for FastRoundResultAccountData {}
71
72impl FastRoundResultAccountData {
73    pub fn serialize<W>(&self, writer: &mut W) -> Result<(), Error> where W: Write {
74        let buf = bytes_of(self).to_vec();
75        let mut res: Vec<u8> = vec![
76            SwitchboardAccountType::TYPE_AGGREGATOR_RESULT_PARSE_OPTIMIZED as u8];
77        res.extend(buf);
78        writer.write_all(&res)?;
79        Ok(())
80    }
81
82    pub fn deserialize(buf: &[u8]) -> Result<Self, std::io::Error> {
83        if buf.len() == 0 || buf[0] != SwitchboardAccountType::TYPE_AGGREGATOR_RESULT_PARSE_OPTIMIZED as u8 {
84            return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid buffer account type"));
85        }
86        let buf = &buf[1..];
87        let mut res = FastRoundResultAccountData {..Default::default()};
88        let recv = bytes_of_mut(&mut res);
89        if buf.len() < recv.len() {
90            return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid buffer length"));
91        }
92        recv.copy_from_slice(&buf[..recv.len()]);
93        return Ok(res);
94    }
95}
96
97pub fn fast_parse_switchboard_result(bytes: &[u8]) -> FastRoundResultAccountData {
98    let res = FastRoundResultAccountData::deserialize(bytes).unwrap();
99    return res;
100}
101
102pub fn save_state<T: MessageWrite>(
103    account: &AccountInfo,
104    state: &T,
105    type_indicator: SwitchboardAccountType,
106) -> Result<Vec<u8>, ProgramError> {
107    let mut to = account.try_borrow_mut_data()?;
108    let mut bytes = serialize_into_vec(state).map_err(|_| ProgramError::InvalidAccountData)?;
109    let mut res = vec![type_indicator as u8];
110    res.append(&mut bytes);
111    to[..res.len()].copy_from_slice(&res);
112    Ok([].to_vec())
113}
114
115pub fn save_state_to_buffer<T: MessageWrite>(
116    state: &T,
117    type_indicator: SwitchboardAccountType,
118) -> Result<Vec<u8>, ProgramError> {
119    let bytes = serialize_into_vec(state).map_err(|_| ProgramError::InvalidAccountData)?;
120    Ok(format_buffer(&bytes, type_indicator))
121}
122
123pub fn format_buffer(from: &[u8], type_indicator: SwitchboardAccountType) -> Vec<u8> {
124    let mut res = vec![type_indicator as u8];
125    res.extend(from);
126    res
127}
128
129pub fn memcpy(to: &mut [u8], from: &[u8]) {
130    to[..from.len()].copy_from_slice(&from);
131}
132
133pub fn is_account_rent_exempt(
134    rent_sysvar_account: &AccountInfo,
135    account: &AccountInfo,
136) -> Result<bool, ProgramError> {
137    let data_len = account.try_data_len()?;
138    let lamports = account.try_lamports()?;
139    is_rent_exempt(rent_sysvar_account, lamports, data_len)
140}
141
142pub fn is_rent_exempt(
143    rent_sysvar_account: &AccountInfo,
144    lamports: u64,
145    data_len: usize,
146) -> Result<bool, ProgramError> {
147    if *rent_sysvar_account.key != rent::ID {
148        msg!("Incorrect rent sysvar account.");
149        return Err(ProgramError::IncorrectProgramId);
150    }
151    let rent = rent::Rent::from_account_info(rent_sysvar_account)?;
152    if rent.is_exempt(lamports, data_len as usize) {
153        return Ok(true);
154    }
155    Ok(false)
156}
157
158pub trait ProgramAction {
159    fn validate(&self) -> Result<(), ProgramError>;
160    fn actuate(&mut self) -> Result<Vec<u8>, ProgramError>;
161}
162
163pub struct AccountType {}
164
165impl AccountType {
166    pub fn check_account(
167        account: &AccountInfo,
168        type_indicator: SwitchboardAccountType,
169    ) -> Result<(), ProgramError> {
170        let buffer = account.try_borrow_data()?;
171        AccountType::strip_type_byte(&buffer, type_indicator)?;
172        Ok(())
173    }
174
175    pub fn check_account_buffer(
176        buf: &[u8],
177        type_indicator: SwitchboardAccountType,
178    ) -> Result<(), ProgramError> {
179        AccountType::strip_type_byte(buf, type_indicator)?;
180        Ok(())
181    }
182
183    pub fn strip_type_byte<'a>(
184        buf: &'a [u8],
185        type_indicator: SwitchboardAccountType,
186    ) -> Result<&'a [u8], ProgramError> {
187        if buf[0] != type_indicator as u8 {
188            msg!("Account type {:?} != {:?}", buf[0], type_indicator.clone());
189            return Err(ProgramError::InvalidAccountData);
190        }
191        Ok(&buf[1..])
192    }
193
194    pub fn set_type_byte(buf: &mut [u8], type_indicator: SwitchboardAccountType) -> Result<(), ProgramError> {
195        let type_indicator_byte = type_indicator as u8;
196        if buf[0] != SwitchboardAccountType::TYPE_UNINITIALIZED as u8 && buf[0] != type_indicator_byte {
197            msg!("Invalid account type change");
198            return Err(ProgramError::AccountAlreadyInitialized);
199        }
200        buf[0] = type_indicator_byte;
201        Ok(())
202    }
203}
204
205pub mod mod_test_utils {
206    use super::*;
207
208    pub fn new_account_info<'a>(
209        owner: &'a Pubkey,
210        key: &'a Pubkey,
211        lamports: &'a mut u64,
212        data: &'a mut [u8],
213    ) -> AccountInfo<'a> {
214        AccountInfo::new(
215            key,      // key: &'a Pubkey,
216            false,    // is_signer: bool,
217            true,     // is_writable: bool,
218            lamports, // lamports: &'a mut u64,
219            data,     // data: &'a mut [u8],
220            owner,    // owner: &'a Pubkey,
221            false,    // executable: bool,
222            100,      // rent_epoch: Epoch
223        )
224    }
225
226    pub fn create_aggregator() -> AggregatorState {
227        AggregatorState {
228            version: Some(1),
229            configs: Some(mod_AggregatorState::Configs {
230                locked: Some(false),
231                min_confirmations: Some(0),
232                min_update_delay_seconds: Some(0),
233                schedule: None,
234            }),
235            fulfillment_manager_pubkey: Some(Vec::new()),
236            job_definition_pubkeys: Vec::new(),
237            agreement: None,
238            current_round_result: None,
239            last_round_result: None,
240            parse_optimized_result_address: None,
241            bundle_auth_addresses: Vec::new(),
242        }
243    }
244
245    pub fn create_fm() -> FulfillmentManagerState {
246        FulfillmentManagerState {
247            version: Some(1),
248            configs: Some(mod_FulfillmentManagerState::Configs {
249                heartbeat_auth_required: Some(false),
250                usage_auth_required: Some(false),
251                locked: Some(false),
252            }),
253            entries: Vec::new(),
254        }
255    }
256
257}