-
Notifications
You must be signed in to change notification settings - Fork 301
Add publisher program #1854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add publisher program #1854
Changes from 5 commits
b91ba09
258e5ea
b34547d
1fa5089
e487dfd
8f0f2ca
af2631f
3a7e5ea
4df7172
cc0cdcb
b25700b
e88e3f6
2e90868
9e1ce6a
aaa613a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
|
@@ -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, | ||
|
@@ -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 { | ||
|
@@ -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; | ||
|
@@ -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); | ||
} | ||
|
@@ -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>() { | ||
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
There was a problem hiding this comment.
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).