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