1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
use crate::cfg_client;
use crate::prelude::*;
use bytemuck::try_cast_slice_mut;
use std::cell::Ref;

#[account(zero_copy(unsafe))]
#[repr(packed)]
pub struct OracleQueueAccountData {
    /// Name of the queue to store on-chain.
    pub name: [u8; 32],
    /// Metadata of the queue to store on-chain.
    pub metadata: [u8; 64],
    /// The account delegated as the authority for making account changes or assigning permissions targeted at the queue.
    pub authority: Pubkey,
    /// Interval when stale oracles will be removed if they fail to heartbeat.
    pub oracle_timeout: u32,
    /// Rewards to provide oracles and round openers on this queue.
    pub reward: u64,
    /// The minimum amount of stake oracles must present to remain on the queue.
    pub min_stake: u64,
    /// Whether slashing is enabled on this queue.
    pub slashing_enabled: bool,
    /// The tolerated variance amount oracle results can have from the accepted round result before being slashed.
    /// slashBound = varianceToleranceMultiplier * stdDeviation Default: 2
    pub variance_tolerance_multiplier: SwitchboardDecimal,
    /// Number of update rounds new feeds are on probation for.
    /// If a feed returns 429s within probation period, auto disable permissions.
    pub feed_probation_period: u32,
    //
    /// Current index of the oracle rotation.
    pub curr_idx: u32,
    /// Current number of oracles on a queue.
    pub size: u32,
    /// Garbage collection index.
    pub gc_idx: u32,
    /// Consecutive failure limit for a feed before feed permission is revoked.
    pub consecutive_feed_failure_limit: u64,
    /// Consecutive failure limit for an oracle before oracle permission is revoked.
    pub consecutive_oracle_failure_limit: u64,
    /// Enabling this setting means data feeds do not need explicit permission to join the queue and request new values from its oracles.
    pub unpermissioned_feeds_enabled: bool,
    /// Enabling this setting means VRF accounts do not need explicit permission to join the queue and request new values from its oracles.
    pub unpermissioned_vrf_enabled: bool,
    /// TODO: Revenue percentage rewarded to job curators overall.
    pub curator_reward_cut: SwitchboardDecimal,
    /// Prevent new leases from being funded n this queue.
    /// Useful to turn down a queue for migrations, since authority is always immutable.
    pub lock_lease_funding: bool,
    /// Token mint used for the oracle queue rewards and slashing.
    pub mint: Pubkey,
    /// Whether oracles are permitted to fulfill buffer relayer update request.
    pub enable_buffer_relayers: bool,
    /// Reserved for future info.
    pub _ebuf: [u8; 968],
    /// Maximum number of oracles a queue can support.
    pub max_size: u32,
    /// The public key of the OracleQueueBuffer account holding a collection of Oracle pubkeys that haver successfully heartbeated before the queues `oracleTimeout`.
    pub data_buffer: Pubkey,
}

impl Default for OracleQueueAccountData {
    fn default() -> Self {
        unsafe { std::mem::zeroed() }
    }
}

impl OracleQueueAccountData {
    pub fn size() -> usize {
        std::mem::size_of::<OracleQueueAccountData>() + 8
    }

    pub fn convert_buffer(buf: &mut [u8]) -> &mut [Pubkey] {
        try_cast_slice_mut(&mut buf[8..]).unwrap()
    }

    pub fn len(&self) -> u32 {
        self.size
    }

    pub fn is_empty(&self) -> bool {
        self.size == 0
    }

    pub fn get_mint(&self) -> Pubkey {
        if self.mint == Pubkey::default() {
            return anchor_spl::token::spl_token::ID;
        }
        self.mint
    }

    pub fn max_round_rewards(&self, batch_size: u32) -> u64 {
        self.reward
            .checked_mul(batch_size.checked_add(1).unwrap().into())
            .unwrap()
    }

    /// Returns the deserialized Switchboard OracleQueue account
    ///
    /// # Arguments
    ///
    /// * `account_info` - A Solana AccountInfo referencing an existing Switchboard OracleQueue
    ///
    /// # Examples
    ///
    /// ```ignore
    /// use switchboard_solana::OracleQueueAccountData;
    ///
    /// let oracle_queue = OracleQueueAccountData::new(queue_account_info)?;
    /// ```
    pub fn new<'info>(
        account_info: &'info AccountInfo<'info>,
    ) -> anchor_lang::Result<Ref<'info, Self>> {
        let data = account_info.try_borrow_data()?;
        if data.len() < OracleQueueAccountData::discriminator().len() {
            return Err(ErrorCode::AccountDiscriminatorNotFound.into());
        }

        let mut disc_bytes = [0u8; 8];
        disc_bytes.copy_from_slice(&data[..8]);
        if disc_bytes != OracleQueueAccountData::discriminator() {
            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
        }

        Ok(Ref::map(data, |data| {
            bytemuck::from_bytes(&data[8..std::mem::size_of::<OracleQueueAccountData>() + 8])
        }))
    }

    /// Returns the deserialized Switchboard OracleQueue account
    ///
    /// # Arguments
    ///
    /// * `data` - A Solana AccountInfo's data buffer
    ///
    /// # Examples
    ///
    /// ```ignore
    /// use switchboard_solana::OracleQueueAccountData;
    ///
    /// let oracle_queue = OracleQueueAccountData::new(oracle_account_info.try_borrow_data()?)?;
    /// ```
    pub fn new_from_bytes(data: &[u8]) -> anchor_lang::Result<&OracleQueueAccountData> {
        if data.len() < OracleQueueAccountData::discriminator().len() {
            return Err(ErrorCode::AccountDiscriminatorNotFound.into());
        }

        let mut disc_bytes = [0u8; 8];
        disc_bytes.copy_from_slice(&data[..8]);
        if disc_bytes != OracleQueueAccountData::discriminator() {
            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
        }

        Ok(bytemuck::from_bytes(
            &data[8..std::mem::size_of::<OracleQueueAccountData>() + 8],
        ))
    }

    cfg_client! {
        pub fn fetch(
            client: &solana_client::rpc_client::RpcClient,
            pubkey: Pubkey,
        ) -> std::result::Result<Self, switchboard_common::SbError> {
            crate::client::fetch_zerocopy_account(client, pubkey)
        }

        pub async fn fetch_async(
            client: &solana_client::nonblocking::rpc_client::RpcClient,
            pubkey: Pubkey,
        ) -> std::result::Result<Self, switchboard_common::SbError> {
            crate::client::fetch_zerocopy_account_async(client, pubkey).await
        }

        pub fn fetch_sync<T: solana_sdk::client::SyncClient>(
            client: &T,
            pubkey: Pubkey,
        ) -> std::result::Result<Self, switchboard_common::SbError> {
            crate::client::fetch_zerocopy_account_sync(client, pubkey)
        }

        pub async fn fetch_buffer(&self,
            client: &solana_client::nonblocking::rpc_client::RpcClient,
            ) -> Result<Vec<Pubkey>, SwitchboardError> {
            let buffer = client.get_account_data(&self.data_buffer).await
                .map_err(|_| SwitchboardError::NetworkError)?;
            let buffer: &[Pubkey] = bytemuck::try_cast_slice(&buffer.get(8..).unwrap_or(&[]))
                .map_err(|_| SwitchboardError::AccountDeserializationError)?;
            Ok(buffer[..self.size as usize].to_vec())
        }

        pub async fn fetch_garbage_collection_key(&self,
            client: &solana_client::nonblocking::rpc_client::RpcClient,
            ) -> Result<Option<Pubkey>, SwitchboardError> {
            let buf = self.fetch_buffer(client).await?;
            let gc_oracle = *buf.get(self.gc_idx as usize).unwrap_or(&Pubkey::default());
            if gc_oracle != Pubkey::default() {
                Ok(Some(gc_oracle))
            } else {
                Ok(None)
            }
        }
    }
}