Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions target_chains/solana/programs/pyth-price-publisher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ version = "0.1.0"
edition = "2021"

[dependencies]
bytemuck = "1.13.0"
solana-program = "=1.14.17"
bytemuck = { version = "1.13.0", features = ["derive"] }
solana-program = { version = "=1.14.17", optional = true }
thiserror = "1.0.40"

[dev-dependencies]
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
pub mod publisher_prices;
pub mod config;
pub mod errors;
pub mod publisher_config;
pub mod buffer;
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@
//! to allow the validator to stay in sync.

use {
super::errors::{ExtendError, PublisherPriceError, ReadAccountError},
bytemuck::{cast_slice, from_bytes, from_bytes_mut, Pod, Zeroable},
solana_program::clock::Slot,
std::mem::size_of,
thiserror::Error,
};

/// Account Magic to avoid Account Confusiong
const FORMAT: u32 = 2848712303;

#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroable, Pod)]
#[repr(C, packed)]
pub struct PublisherPricesHeader {
pub struct BufferHeader {
pub format: u32,
pub publisher: [u8; 32],
pub slot: Slot,
pub slot: u64,
pub num_prices: u32,
}

impl PublisherPricesHeader {
impl BufferHeader {
pub fn new(publisher: [u8; 32]) -> Self {
PublisherPricesHeader {
BufferHeader {
format: FORMAT,
publisher,
slot: 0,
Expand All @@ -32,28 +31,24 @@ impl PublisherPricesHeader {
}
}

#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroable, Pod)]
#[repr(C, packed)]
pub struct PublisherPrice {
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,
}

#[derive(Debug, Error)]
#[error("publisher price data overflow")]
pub struct PublisherPriceError;

impl PublisherPrice {
impl BufferedPrice {
pub fn new(
feed_index: u32,
trading_status: u32,
price: i64,
confidence: u64,
) -> Result<Self, PublisherPriceError> {
if feed_index >= (1 << 28) || trading_status >= (1 << 4) {
if feed_index == 0 || feed_index >= (1 << 28) || trading_status >= (1 << 4) {
return Err(PublisherPriceError);
}
Ok(Self {
Expand All @@ -72,24 +67,6 @@ impl PublisherPrice {
}
}

#[derive(Debug, Error)]
pub enum ReadAccountError {
#[error("data too short")]
DataTooShort,
#[error("format mismatch")]
FormatMismatch,
#[error("invalid num prices")]
InvalidNumPrices,
}

#[derive(Debug, Error)]
pub enum ExtendError {
#[error("not enough space")]
NotEnoughSpace,
#[error("invalid length")]
InvalidLength,
}

pub fn format_matches(data: &[u8]) -> bool {
if data.len() < size_of::<u32>() {
return false;
Expand All @@ -98,36 +75,36 @@ pub fn format_matches(data: &[u8]) -> bool {
*format == FORMAT
}

pub fn read(data: &[u8]) -> Result<(&PublisherPricesHeader, &[PublisherPrice]), ReadAccountError> {
if data.len() < size_of::<PublisherPricesHeader>() {
pub fn read(data: &[u8]) -> Result<(&BufferHeader, &[BufferedPrice]), ReadAccountError> {
if data.len() < size_of::<BufferHeader>() {
return Err(ReadAccountError::DataTooShort);
}
let header: &PublisherPricesHeader = from_bytes(&data[..size_of::<PublisherPricesHeader>()]);
let header: &BufferHeader = from_bytes(&data[..size_of::<BufferHeader>()]);
if header.format != FORMAT {
return Err(ReadAccountError::FormatMismatch);
}
let prices_bytes = &data[size_of::<PublisherPricesHeader>()..];
let prices_bytes = &data[size_of::<BufferHeader>()..];
let num_prices: usize = header.num_prices.try_into().unwrap();
let expected_len = num_prices.saturating_mul(size_of::<PublisherPrice>());
let expected_len = num_prices.saturating_mul(size_of::<BufferedPrice>());
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))
}

pub fn size(max_prices: usize) -> usize {
size_of::<PublisherPricesHeader>() + max_prices * size_of::<PublisherPrice>()
size_of::<BufferHeader>() + max_prices * size_of::<BufferedPrice>()
}

pub fn read_mut(
data: &mut [u8],
) -> Result<(&mut PublisherPricesHeader, &mut [u8]), ReadAccountError> {
if data.len() < size_of::<PublisherPricesHeader>() {
pub fn read_mut(data: &mut [u8]) -> Result<(&mut BufferHeader, &mut [u8]), ReadAccountError> {
if data.len() < size_of::<BufferHeader>() {
return Err(ReadAccountError::DataTooShort);
}
let (header, prices) = data.split_at_mut(size_of::<PublisherPricesHeader>());
let header: &mut PublisherPricesHeader = from_bytes_mut(header);
let (header, prices) = data.split_at_mut(size_of::<BufferHeader>());
let header: &mut BufferHeader = from_bytes_mut(header);
if header.format != FORMAT {
return Err(ReadAccountError::FormatMismatch);
}
Expand All @@ -137,37 +114,47 @@ pub fn read_mut(
pub fn create(
data: &mut [u8],
publisher: [u8; 32],
) -> Result<(&mut PublisherPricesHeader, &mut [u8]), ReadAccountError> {
if data.len() < size_of::<PublisherPricesHeader>() {
) -> Result<(&mut BufferHeader, &mut [u8]), ReadAccountError> {
if data.len() < size_of::<BufferHeader>() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid errors, wouldn't it make more sense to ensure that data can hold at least one price? I think a buffer that can only hold the header is pretty useless, as it will never be able to publish any prices (and in the current design such a mistake would be relatively bad because the buffer cannot be resized).

return Err(ReadAccountError::DataTooShort);
}
let (header, prices) = data.split_at_mut(size_of::<PublisherPricesHeader>());
let header: &mut PublisherPricesHeader = from_bytes_mut(header);
*header = PublisherPricesHeader::new(publisher);
let (header, prices) = data.split_at_mut(size_of::<BufferHeader>());
let header: &mut BufferHeader = from_bytes_mut(header);
if header.format != 0 {
return Err(ReadAccountError::AlreadyInitialized);
}
*header = BufferHeader::new(publisher);
Ok((header, prices))
}

pub fn extend(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort of a nitpick but I think "extend" isn't a good name for this -- "extend" usually implies that you will add more data without overwriting existing data. I think "update" or "update_prices" would be a better name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #1883.

header: &mut PublisherPricesHeader,
header: &mut BufferHeader,
prices: &mut [u8],
new_prices: &[u8],
) -> Result<(), ExtendError> {
if new_prices.len() % size_of::<PublisherPrice>() != 0 {
if new_prices.len() % size_of::<BufferedPrice>() != 0 {
return Err(ExtendError::InvalidLength);
}
let num_new_prices = (new_prices.len() / size_of::<PublisherPrice>())
let num_new_prices = (new_prices.len() / size_of::<BufferedPrice>())
.try_into()
.expect("unexpected overflow");
let num_prices: usize = header.num_prices.try_into().unwrap();
let start = size_of::<PublisherPrice>() * num_prices;
let end = size_of::<PublisherPrice>() * num_prices + new_prices.len();
let start = size_of::<BufferedPrice>() * num_prices;
let end = size_of::<BufferedPrice>() * num_prices + new_prices.len();
header.num_prices = header
.num_prices
.checked_add(num_new_prices)
.expect("unexpected overflow");
prices

let destination = prices
.get_mut(start..end)
.ok_or(ExtendError::NotEnoughSpace)?
.copy_from_slice(new_prices);
.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(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use bytemuck::{from_bytes, from_bytes_mut, Pod, Zeroable};
use std::mem::size_of;

use super::errors::ReadAccountError;

/// Account Magic to avoid Account Confusiong
const FORMAT: u32 = 1505352794;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroable, Pod)]
#[repr(C, packed)]
pub struct Config {
pub format: u32,
pub authority: [u8; 32],
}

pub const SIZE: usize = size_of::<Config>();

pub fn read(data: &[u8]) -> Result<&Config, ReadAccountError> {
if data.len() < size_of::<Config>() {
return Err(ReadAccountError::DataTooShort);
}
let data: &Config = from_bytes(&data[..size_of::<Config>()]);
if data.format != FORMAT {
return Err(ReadAccountError::FormatMismatch);
}
Ok(data)
}

pub fn create(data: &mut [u8], authority: [u8; 32]) -> Result<&mut Config, ReadAccountError> {
if data.len() < size_of::<Config>() {
return Err(ReadAccountError::DataTooShort);
}
let data: &mut Config = from_bytes_mut(&mut data[..size_of::<Config>()]);
if data.format != 0 {
return Err(ReadAccountError::AlreadyInitialized);
}
data.format = FORMAT;
data.authority = authority;
Ok(data)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ReadAccountError {
#[error("data too short")]
DataTooShort,
#[error("format mismatch")]
FormatMismatch,
#[error("already initialized")]
AlreadyInitialized,
#[error("invalid num prices")]
InvalidNumPrices,
}

#[derive(Debug, Error)]
#[error("publisher price data overflow")]
pub struct PublisherPriceError;

#[derive(Debug, Error)]
pub enum ExtendError {
#[error("not enough space")]
NotEnoughSpace,
#[error("invalid length")]
InvalidLength,
}

#[cfg(feature = "solana-program")]
mod convert {
use super::*;
use solana_program::program_error::ProgramError;

impl From<ReadAccountError> for ProgramError {
fn from(value: ReadAccountError) -> Self {
match value {
ReadAccountError::DataTooShort => ProgramError::AccountDataTooSmall,
ReadAccountError::FormatMismatch => ProgramError::InvalidAccountData,
ReadAccountError::AlreadyInitialized => ProgramError::AccountAlreadyInitialized,
ReadAccountError::InvalidNumPrices => ProgramError::InvalidAccountData,
}
}
}

impl From<PublisherPriceError> for ProgramError {
fn from(_value: PublisherPriceError) -> Self {
ProgramError::AccountDataTooSmall
}
}

impl From<ExtendError> for ProgramError {
fn from(value: ExtendError) -> Self {
match value {
ExtendError::NotEnoughSpace => ProgramError::AccountDataTooSmall,
ExtendError::InvalidLength => ProgramError::InvalidInstructionData,
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use bytemuck::{from_bytes, from_bytes_mut, Pod, Zeroable};
use std::mem::size_of;

use super::errors::ReadAccountError;

/// Account Magic to avoid Account Confusiong
const FORMAT: u32 = 2258188348;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroable, Pod)]
#[repr(C, packed)]
pub struct PublisherConfig {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add some docs on what is this account and how it's used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments about accounts and instructions.

pub format: u32,
pub buffer_account: [u8; 32],
}

pub const SIZE: usize = size_of::<PublisherConfig>();

pub fn read(data: &[u8]) -> Result<&PublisherConfig, ReadAccountError> {
if data.len() < size_of::<PublisherConfig>() {
return Err(ReadAccountError::DataTooShort);
}
let data: &PublisherConfig = from_bytes(&data[..size_of::<PublisherConfig>()]);
if data.format != FORMAT {
return Err(ReadAccountError::FormatMismatch);
}
Ok(data)
}

pub fn create(
data: &mut [u8],
buffer_account: [u8; 32],
) -> Result<&mut PublisherConfig, ReadAccountError> {
if data.len() < size_of::<PublisherConfig>() {
return Err(ReadAccountError::DataTooShort);
}
let data: &mut PublisherConfig = from_bytes_mut(&mut data[..size_of::<PublisherConfig>()]);
if data.format != 0 {
return Err(ReadAccountError::AlreadyInitialized);
}
data.format = FORMAT;
data.buffer_account = buffer_account;
Ok(data)
}
Loading