switchboard-on-demand 0.7.0

A Rust library to interact with the Switchboard Solana program.
Documentation
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
//! Oracle quote verification and data extraction functionality
//!
//! This module provides the core `OracleQuote` struct for working with verified oracle data quotes.
//! A quote contains aggregated feed data from multiple oracles, cryptographically verified
//! through ED25519 signatures and Solana's instruction sysvar.

use anyhow::{Context, Error as AnyError};
use core::ptr::read_unaligned;
use solana_define_syscall::definitions::sol_memcpy_;
use solana_program::sysvar::clock::Clock;
use solana_program::account_info::AccountInfo;
use solana_program::ed25519_program::ID as ED25519_PROGRAM_ID;
use solana_program::sysvar::instructions::ID as INSTRUCTIONS_SYSVAR_ID;
use crate::Instructions;
use crate::check_pubkey_eq;
use crate::QuoteVerifier;

#[allow(unused)]
const SLOTS_PER_EPOCH: u64 = 432_000;


/// Default discriminator for Switchboard Oracle data
pub const QUOTE_DISCRIMINATOR: [u8; 8] = *b"SBOracle";
/// QUOTE_DISCRIMINATOR as little-endian u64 for efficient comparison
pub const QUOTE_DISCRIMINATOR_U64_LE: u64 = u64::from_le_bytes(QUOTE_DISCRIMINATOR);

/// A verified oracle quote containing feed data from multiple oracles.
///
/// This struct provides zero-copy access to aggregated oracle feed data that has been
/// cryptographically verified through ED25519 signatures. The quote contains:
/// - Feed data with values and metadata
/// - Oracle signature information and indices
/// - Slot and version information for freshness validation
/// - Raw instruction data for serialization (when available)
///
/// All data is stored as references to avoid unnecessary copying, making this struct
/// highly efficient for on-chain programs where compute units are precious.
#[derive(Clone, Copy)]
pub struct OracleQuote<'a> {
    /// Reference to the quote header containing signed slot hash
    quote_header_refs: &'a crate::on_demand::oracle_quote::feed_info::PackedQuoteHeader,
    /// Number of oracle signatures that verified this quote
    pub oracle_count: u8,
    /// Zero-copy reference to the packed feed data from the first signature
    pub packed_feed_infos: &'a [crate::on_demand::oracle_quote::feed_info::PackedFeedInfo],
    /// Number of valid feeds in the quote (private, calculated during verification)
    feed_count: u8,
    /// Oracle indices that correspond to the queue's oracle array
    pub oracle_idxs: &'a [u8],
    /// Recent slot from the ED25519 instruction data used for freshness validation
    pub recent_slot: u64,
    /// Version from the ED25519 instruction data
    pub version: u8,
    /// Reference to the raw ED25519 instruction data for serialization
    pub raw_buffer: Option<&'a [u8]>,
}

impl<'a> OracleQuote<'a> {
    /// Creates a new OracleQuote with header references and zero-copy feed data.
    ///
    /// This constructor is used internally after verification to create an OracleQuote
    /// instance with validated data. All parameters should be pre-verified.
    ///
    /// # Arguments
    /// * `quote_header_ref` - Reference to the verified quote header
    /// * `oracle_count` - Number of oracle signatures
    /// * `packed_feed_infos` - Slice of packed feed information
    /// * `feed_count` - Number of valid feeds
    /// * `oracle_idxs` - Oracle indices array
    /// * `recent_slot` - Recent slot from ED25519 instruction
    /// * `version` - Version from ED25519 instruction
    /// * `raw_buffer` - Reference to the raw ED25519 instruction data
    #[inline(always)]
    pub(crate) fn new(
        quote_header_ref: &'a crate::on_demand::oracle_quote::feed_info::PackedQuoteHeader,
        oracle_count: u8,
        packed_feed_infos: &'a [crate::on_demand::oracle_quote::feed_info::PackedFeedInfo],
        feed_count: u8,
        oracle_idxs: &'a [u8],
        recent_slot: u64,
        version: u8,
        raw_buffer: Option<&'a [u8]>,
    ) -> Self {
        Self {
            quote_header_refs: quote_header_ref,
            oracle_count,
            packed_feed_infos,
            feed_count,
            oracle_idxs,
            recent_slot,
            version,
            raw_buffer,
        }
    }

    /// Returns the recent slot from the ED25519 instruction data.
    ///
    /// This slot value represents when the quote was created and is used
    /// for freshness validation against the slot hash sysvar.
    #[inline(always)]
    pub fn slot(&self) -> u64 {
        self.recent_slot
    }

    /// Returns the version from the ED25519 instruction data.
    ///
    /// The version indicates the quote format version used by the oracles.
    #[inline(always)]
    pub fn version(&self) -> u8 {
        self.version
    }

    /// Returns a reference to the raw ED25519 instruction data used to create this quote.
    ///
    /// This method provides access to the original verified instruction data that can be
    /// used for serialization, storage, or further processing. The data includes all
    /// signatures and quote information in its original binary format.
    ///
    /// # Returns
    /// * `Some(&[u8])` - Reference to the raw instruction data if available
    /// * `None` - Raw data not available (e.g., quote created from account data)
    ///
    /// # Example
    /// ```rust,ignore
    /// let quote = verifier.load_and_verify(0)?;
    ///
    /// if let Some(raw_data) = quote.raw_data() {
    ///     // Store or transmit the raw oracle data
    ///     store_oracle_quote(raw_data)?;
    /// }
    /// ```
    #[inline(always)]
    pub fn raw_data(&self) -> Option<&[u8]> {
        self.raw_buffer
    }

    /// Returns a slice of the valid packed feeds.
    ///
    /// This provides access to all verified feed data in the quote.
    /// Each feed contains a feed ID, value, and minimum oracle samples requirement.
    #[inline(always)]
    pub fn feeds(&self) -> &[crate::on_demand::oracle_quote::feed_info::PackedFeedInfo] {
        &self.packed_feed_infos[..self.feed_count as usize]
    }

    /// Returns the number of valid feeds in this quote
    #[inline(always)]
    pub fn len(&self) -> usize {
        self.feed_count as usize
    }

    /// Returns true if this quote contains no feeds
    #[inline(always)]
    pub fn is_empty(&self) -> bool {
        self.feed_count == 0
    }

    /// Returns the oracle index for a specific signature position.
    ///
    /// # Arguments
    /// * `signature_index` - The position of the signature (0 to oracle_count-1)
    ///
    /// # Returns
    /// * `Ok(u8)` - The oracle index that corresponds to the queue's oracle array
    /// * `Err(AnyError)` - If signature_index is out of bounds
    ///
    /// # Example
    /// ```rust,ignore
    /// let oracle_idx = quote.oracle_index(0)?; // Get first oracle's index
    /// ```
    #[inline(always)]
    pub fn oracle_index(&self, signature_index: usize) -> Result<u8, AnyError> {
        if signature_index < self.oracle_count as usize {
            Ok(self.oracle_idxs[signature_index])
        } else {
            anyhow::bail!(
                "Invalid signature index {} for quote with {} oracles",
                signature_index, self.oracle_count
            )
        }
    }

    /// Returns a reference to the quote header.
    ///
    /// The header contains the signed slot hash that was verified against
    /// the slot hash sysvar during quote verification.
    #[inline(always)]
    pub fn header(
        &self,
    ) -> &'a crate::on_demand::oracle_quote::feed_info::PackedQuoteHeader {
        self.quote_header_refs
    }

    /// Finds a packed feed with a specific feed ID.
    ///
    /// # Arguments
    /// * `feed_id` - A 32-byte array representing the feed ID to look for
    ///
    /// # Returns
    /// * `Ok(&PackedFeedInfo)` - Reference to the feed info if found
    /// * `Err(AnyError)` - Error if the feed ID is not found in the quote
    ///
    /// # Example
    /// ```rust,ignore
    /// let feed_id = [0u8; 32]; // Your feed ID
    /// match quote.feed(&feed_id) {
    ///     Ok(feed_info) => println!("Feed value: {}", feed_info.value()),
    ///     Err(_) => println!("Feed not found in quote"),
    /// }
    /// ```
    #[inline(always)]
    pub fn feed(
        &self,
        feed_id: &[u8; 32],
    ) -> std::result::Result<&crate::on_demand::oracle_quote::feed_info::PackedFeedInfo, AnyError> {
        let info = self.packed_feed_infos[..self.feed_count as usize]
            .iter()
            .find(|info| info.feed_id() == feed_id)
            .context("Switchboard On-Demand FeedNotFound")?;
        Ok(info)
    }

    /// High-performance ED25519 instruction data copy with slot validation.
    ///
    /// This function performs an optimized copy of oracle quote data from ED25519 instruction data
    /// to a destination buffer with a length prefix. It implements slot-based ordering validation 
    /// to prevent oracle quote replay attacks and ensures data freshness.
    ///
    /// # Data Format
    ///
    /// **Source format** (ED25519 instruction data):
    /// ```
    /// [message_data][oracle_signatures][recent_slot(8)][version(1)][SBOD(4)]
    /// ```
    /// - Slot is located at offset `data_len - 13` (13 = 8 + 1 + 4)
    ///
    /// **Destination format** (after this function):
    /// ```
    /// [length(2)][message_data][oracle_signatures][recent_slot(8)][version(1)][SBOD(4)]
    /// ```
    /// - Adds 2-byte length prefix to the instruction data
    ///
    /// # Arguments
    ///
    /// * `clock` - Solana clock for slot validation
    /// * `source` - ED25519 instruction data slice containing oracle quote
    /// * `dst` - Mutable destination buffer (will be prefixed with 2-byte length)
    ///
    /// # Safety
    ///
    /// This function performs unsafe memory operations for performance:
    /// - **ASSUMES** `source` contains valid ED25519 instruction data with slot at correct offset
    /// - **ASSUMES** `dst` buffer has sufficient capacity (source.len() + 2 bytes)
    /// - **REQUIRES** instruction data format: [...data][slot(8)][version(1)][SBOD(4)]
    ///
    /// # Validation
    ///
    /// Performs critical slot-based validations:
    /// - **Freshness**: new slot < clock.slot (prevents stale data)
    /// - **Progression**: new slot ≥ existing slot in destination (anti-replay protection)
    /// - **Capacity**: destination buffer can hold length prefix + data
    ///
    /// # Performance
    ///
    /// Optimized for maximum performance at approximately 79 compute units with validations.
    ///
    /// # Panics
    ///
    /// Panics if critical validations fail:
    /// - New slot >= clock slot (stale oracle data)
    /// - Slot regression detected (replay attack prevention)
    /// - Destination buffer too small for prefixed data
    #[inline(always)]
    fn store_delimited(
        clock: &Clock,
        source: &[u8],
        dst: &mut [u8],
    ) {
        // Validate slot progression before writing
        Self::validate_slot_progression(clock, source, dst);

        // 79 Compute units with safety checks and sequencing
        unsafe {
            let dst_ptr = dst.as_mut_ptr();
            let data_len = source.len();

            // Write the new data
            assert!(data_len + 2 <= dst.len()); // ensure dst buffer is large enough
            *(dst_ptr as *mut u16) = data_len as u16;
            sol_memcpy_(dst_ptr.add(2), source.as_ptr(), data_len as u64);
        }
    }

    /// Validates slot progression before writing oracle data.
    ///
    /// Ensures that:
    /// - New slot >= existing slot in account (no regression)
    /// - New slot < current clock slot (no stale data)
    ///
    /// # Arguments
    /// * `clock` - Current Solana clock
    /// * `source` - New oracle data to write
    /// * `existing_data` - Current account data (may be empty)
    ///
    /// # Panics
    /// Panics if slot validation fails
    #[inline(always)]
    fn validate_slot_progression(clock: &Clock, source: &[u8], existing_data: &[u8]) {
        let source_len = source.len();
        if source_len < 13 {
            panic!("Invalid source data length: {}", source_len);
        }

        unsafe {
            // Extract slot from new data (13 bytes from end: 8 slot + 1 version + 4 SBOD)
            let slot_offset = source_len - 13;
            let new_slot = read_unaligned(source.as_ptr().add(slot_offset) as *const u64);

            // Validate new slot is not stale
            assert!(new_slot < clock.slot,
                "SB oracle slot is stale new_slot: {}, clock.slot: {}", new_slot, clock.slot);

            // Check existing data for slot regression - always calculate from the back
            if existing_data.len() >= 13 { // Minimum data with slot
                let existing_slot_offset = existing_data.len() - 13;
                let existing_slot = read_unaligned(existing_data.as_ptr().add(existing_slot_offset) as *const u64);
                assert!(new_slot >= existing_slot,
                    "SB oracle slot regression new_slot: {}, existing_slot: {}", new_slot, existing_slot);
            }
        }
    }

    /// Writes ED25519 instruction data directly to an oracle account with discriminator.
    ///
    /// This convenience method writes oracle quote data to a target account with the
    /// Switchboard Oracle discriminator prefix. The account data format becomes:
    ///
    /// ```
    /// [discriminator(8)][length(2)][message_data][oracle_signatures][recent_slot(8)][version(1)][SBOD(4)]
    /// ```
    ///
    /// # Arguments
    ///
    /// * `clock` - Solana Clock for slot validation and freshness checks
    /// * `source` - ED25519 instruction data containing oracle quote
    /// * `oracle_account` - Target oracle account to write the data to
    ///
    /// # Safety
    ///
    /// This function assumes:
    /// - Oracle account has sufficient space (at least discriminator + length + source data)
    /// - Minimum 23 bytes (8 discriminator + 2 length + 13 minimum data with slot)
    /// - Performs unsafe memory operations for maximum efficiency
    ///
    /// # Validation
    ///
    /// Performs comprehensive slot validation before writing:
    /// - **Freshness**: new slot < clock.slot (prevents stale data)
    /// - **Progression**: new slot ≥ existing slot in account (prevents replay attacks)
    ///
    /// # Panics
    ///
    /// Panics if the oracle account buffer is too small or slot validation fails.
    #[inline(always)]
    pub fn write(
        clock: &Clock,
        source: &[u8],
        oracle_account: &AccountInfo,
    ) {
        unsafe {
            let dst: &mut [u8] = *oracle_account.data.as_ptr();
            assert!(dst.len() >= 23); // discriminator + u16 + minimum data (13 bytes)

            // Validate slot progression before writing
            Self::validate_slot_progression(clock, source, dst);

            let dst_ptr = dst.as_mut_ptr();
            *(dst_ptr as *mut u64) = QUOTE_DISCRIMINATOR_U64_LE;
            Self::store_delimited(clock, source, &mut dst[8..]);
        }
    }

    /// Writes oracle quote data from an ED25519 instruction to an oracle account.
    ///
    /// This convenience method extracts ED25519 instruction data from the instructions sysvar
    /// and writes it to the target oracle account with proper validation and discriminator.
    ///
    /// # Arguments
    ///
    /// * `ix_sysvar` - Reference to the instructions sysvar account
    /// * `oracle_account` - Target oracle account to write the data to  
    /// * `clock` - Solana Clock for slot validation and freshness checks
    /// * `instruction_index` - Index of the ED25519 instruction to extract (typically 0)
    ///
    /// # Validation
    ///
    /// Performs comprehensive validation:
    /// - **Program ID**: Ensures instruction is from ED25519 program
    /// - **Sysvar ID**: Validates instructions sysvar account
    /// - **Slot progression**: Prevents stale data and replay attacks
    ///
    /// # Panics
    ///
    /// Panics if instruction extraction fails, program ID validation fails, or slot validation fails.
    #[inline(always)]
    pub fn write_from_ix(
        ix_sysvar: &AccountInfo<'a>,
        oracle_account: &AccountInfo,
        clock: &Clock,
        instruction_index: usize,
    ) {
        let (program_id, data) = Instructions::extract_ix_data(ix_sysvar, instruction_index)
            .expect("Failed to extract instruction data");
        assert!(
            check_pubkey_eq(ix_sysvar.key, &INSTRUCTIONS_SYSVAR_ID) &&
            check_pubkey_eq(program_id, &ED25519_PROGRAM_ID));
        Self::write(clock, data, oracle_account);
    }

    /// Loads and verifies an oracle quote from instruction data at the specified index.
    ///
    /// This function extracts ED25519 instruction data from the Instructions sysvar,
    /// validates it's from the ED25519 program, and then verifies the oracle quote
    /// using the provided queue and slot hash sysvar.
    ///
    /// # Arguments
    ///
    /// * `ix_sysvar` - Reference to the Instructions sysvar account
    /// * `queue` - Reference to the oracle queue account containing authorized oracle keys
    /// * `slothash_sysvar` - Reference to the slot hash sysvar for slot validation
    /// * `max_age` - Maximum age in slots for oracle quote freshness validation
    /// * `instruction_index` - Index of the ED25519 instruction to extract and verify
    ///
    /// # Returns
    ///
    /// * `Ok(OracleQuote)` - Verified oracle quote containing feed data
    /// * `Err(AnyError)` - If extraction fails, program ID is invalid, or verification fails
    ///
    /// # Security
    ///
    /// Validates that the instruction comes from the ED25519 program before verification.
    #[inline(always)]
    pub fn load_verified_from_ix<'b>(
        ix_sysvar: &'b AccountInfo<'b>,
        queue: &'b AccountInfo<'b>,
        slothash_sysvar: &'b AccountInfo<'b>,
        max_age: u64,
        instruction_index: usize,
    ) -> Result<OracleQuote<'b>, AnyError> {
        // Extract instruction data and validate program ID
        let (program_id, data) = Instructions::extract_ix_data(ix_sysvar, instruction_index)
            .map_err(|e| anyhow::anyhow!("Failed to extract instruction data: {:?}", e))?;

        assert!(
            check_pubkey_eq(ix_sysvar.key, &INSTRUCTIONS_SYSVAR_ID) &&
            check_pubkey_eq(program_id, &ED25519_PROGRAM_ID));

        // Create verifier first
        let mut verifier = QuoteVerifier::new();
        verifier
            .queue(queue)
            .slothash_sysvar(slothash_sysvar)
            .ix_sysvar(ix_sysvar)
            .max_age(max_age);

        verifier.verify(data)
    }
}