use { super::errors::{ExtendError, PublisherPriceError, ReadAccountError}, bytemuck::{cast_slice, from_bytes, from_bytes_mut, Pod, Zeroable}, std::mem::size_of, }; /// Account Magic to avoid Account Confusiong const FORMAT: u32 = 2848712303; /// A publisher's buffer account. This account acts as the buffer for storing prices sent /// by publishers. It tracks the slot in which it is collecting in order /// to allow the validator to stay in sync. /// Not a PDA because it's not possible /// to create a large (>10240 bytes) PDA, but owned by the publisher program. /// The account's data is represented by a `BufferHeader` followed by /// multiple `BufferedPrice`s. #[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroable, Pod)] #[repr(C, packed)] pub struct BufferHeader { /// Account magic to avoid account confusion. pub format: u32, /// The publisher this buffer is associated with. pub publisher: [u8; 32], /// The slot corresponding to all prices currently stored in the account. /// Determined by the clock value when `SubmitPrices` is called. pub slot: u64, /// The number of prices currently stored in the account. pub num_prices: u32, } impl BufferHeader { pub fn new(publisher: [u8; 32]) -> Self { BufferHeader { format: FORMAT, publisher, slot: 0, num_prices: 0, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroable, Pod)] #[repr(C, packed)] pub struct BufferedPrice { // 4 high bits: trading status // 28 low bits: feed index pub trading_status_and_feed_index: u32, pub price: i64, pub confidence: u64, } impl BufferedPrice { pub fn new( feed_index: u32, trading_status: u32, price: i64, confidence: u64, ) -> Result { if feed_index == 0 || feed_index >= (1 << 28) || trading_status >= (1 << 4) { return Err(PublisherPriceError); } Ok(Self { trading_status_and_feed_index: (trading_status << 28) | feed_index, price, confidence, }) } pub fn trading_status(&self) -> u32 { self.trading_status_and_feed_index >> 28 } pub fn feed_index(&self) -> u32 { self.trading_status_and_feed_index & ((1 << 28) - 1) } } /// Verifies the account magic. pub fn format_matches(data: &[u8]) -> bool { super::format(data).map_or(false, |f| f == FORMAT) } /// Verifies the account size and header. Returns the header and the currently stored prices /// (as indicated by the `num_prices` field in the header). pub fn read(data: &[u8]) -> Result<(&BufferHeader, &[BufferedPrice]), ReadAccountError> { if data.len() < size_of::() { return Err(ReadAccountError::DataTooShort); } let header: &BufferHeader = from_bytes(&data[..size_of::()]); if header.format != FORMAT { return Err(ReadAccountError::FormatMismatch); } let prices_bytes = &data[size_of::()..]; let num_prices: usize = header.num_prices.try_into().unwrap(); let expected_len = num_prices.saturating_mul(size_of::()); if expected_len > prices_bytes.len() { return Err(ReadAccountError::InvalidNumPrices); } // We don't validate the values of `new_prices` to make the publishing process // more efficient. They will be validated when applied in the validator. let prices = cast_slice(&prices_bytes[..expected_len]); Ok((header, prices)) } /// Returns the buffer size required to hold the specified number of prices. pub fn size(max_prices: usize) -> usize { size_of::() + max_prices * size_of::() } /// Verifies the account size and header. Returns the header and the remaining buffer space. /// The remaining space may contain some prices, as indicated by the `num_prices` field in the header. pub fn read_mut(data: &mut [u8]) -> Result<(&mut BufferHeader, &mut [u8]), ReadAccountError> { if data.len() < size_of::() { return Err(ReadAccountError::DataTooShort); } let (header, prices) = data.split_at_mut(size_of::()); let header: &mut BufferHeader = from_bytes_mut(header); if header.format != FORMAT { return Err(ReadAccountError::FormatMismatch); } Ok((header, prices)) } /// Initializes the buffer. Returns the header and the remaining buffer space. pub fn create( data: &mut [u8], publisher: [u8; 32], ) -> Result<(&mut BufferHeader, &mut [u8]), ReadAccountError> { if data.len() < size_of::() { return Err(ReadAccountError::DataTooShort); } let (header, prices) = data.split_at_mut(size_of::()); let header: &mut BufferHeader = from_bytes_mut(header); if header.format != 0 { return Err(ReadAccountError::AlreadyInitialized); } *header = BufferHeader::new(publisher); Ok((header, prices)) } /// Removes prices for the other slot from the buffer (if any) and adds new prices. pub fn update( header: &mut BufferHeader, prices: &mut [u8], current_slot: u64, new_prices: &[u8], ) -> Result<(), ExtendError> { if new_prices.len() % size_of::() != 0 { return Err(ExtendError::InvalidLength); } // We expect the validator to apply the buffered prices at the end of each slot, // so when we observe a new slot we remove previously stored prices. if header.slot != current_slot { header.slot = current_slot; header.num_prices = 0; } let num_new_prices = (new_prices.len() / size_of::()) .try_into() .expect("unexpected overflow"); let num_prices: usize = header.num_prices.try_into().unwrap(); let start = size_of::() * num_prices; let end = size_of::() * num_prices + new_prices.len(); header.num_prices = header .num_prices .checked_add(num_new_prices) .expect("unexpected overflow"); let destination = prices .get_mut(start..end) .ok_or(ExtendError::NotEnoughSpace)?; // We don't validate the values of `new_prices` to make the publishing process // more efficient. They will be validated when applied in the validator. #[cfg(feature = "solana-program")] solana_program::program_memory::sol_memcpy(destination, new_prices, new_prices.len()); #[cfg(not(feature = "solana-program"))] destination.copy_from_slice(new_prices); Ok(()) } #[test] fn test_recommended_size() { // Recommended buffer size, enough to hold 5000 prices. assert_eq!(size(5000), 100048); } #[test] fn test_extend_clears_old_prices() { let mut buf = vec![0u8; size(30)]; create(&mut buf, Default::default()).unwrap(); assert!(read(&buf).unwrap().1.is_empty()); { let (header, prices) = read_mut(&mut buf).unwrap(); update(header, prices, 1, &vec![1; 40]).unwrap(); } assert_eq!(read(&buf).unwrap().1.len(), 2); { let (header, prices) = read_mut(&mut buf).unwrap(); update(header, prices, 1, &vec![1; 60]).unwrap(); } assert_eq!(read(&buf).unwrap().1.len(), 5); { let (header, prices) = read_mut(&mut buf).unwrap(); update(header, prices, 2, &vec![1; 60]).unwrap(); } assert_eq!(read(&buf).unwrap().1.len(), 3); }