Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 program/rust/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ fn do_make_build(targets: Vec<&str>, out_dir: &Path) {
"C oracle make build did not exit with 0 (code
({:?}).\n\nstdout:\n{}\n\nstderr:\n{}",
make_output.status.code(),
String::from_utf8(make_output.stdout).unwrap_or("<non-utf8>".to_owned()),
String::from_utf8(make_output.stderr).unwrap_or("<non-utf8>".to_owned())
String::from_utf8_lossy(&make_output.stdout),
String::from_utf8_lossy(&make_output.stderr)
);
}
}
Expand Down
9 changes: 7 additions & 2 deletions program/rust/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ pub trait PythAccount: Pod {
/// have. `INITIAL_SIZE` <= `minimum_size()`
const MINIMUM_SIZE: usize = size_of::<Self>();

/// Size of the account data on creation. Usually this is the same as `MINIMUM_SIZE` but it's
/// different for `PermissionAccount` because we've added new fields to it. In this case
/// we cannot increase `MINIMUM_SIZE` because that would break reading the account.
const NEW_ACCOUNT_SPACE: usize = Self::MINIMUM_SIZE;

/// Given an `AccountInfo`, verify it is sufficiently large and has the correct discriminator.
fn initialize<'a>(
account: &'a AccountInfo,
Expand Down Expand Up @@ -139,15 +144,15 @@ pub trait PythAccount: Pod {
seeds: &[&[u8]],
version: u32,
) -> Result<(), ProgramError> {
let target_rent = get_rent()?.minimum_balance(Self::MINIMUM_SIZE);
let target_rent = get_rent()?.minimum_balance(Self::NEW_ACCOUNT_SPACE);

if account.data_len() == 0 {
create(
funding_account,
account,
system_program,
program_id,
Self::MINIMUM_SIZE,
Self::NEW_ACCOUNT_SPACE,
target_rent,
seeds,
)?;
Expand Down
31 changes: 28 additions & 3 deletions program/rust/src/accounts/permission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@ use {
Pod,
Zeroable,
},
solana_program::pubkey::Pubkey,
std::mem::size_of,
solana_program::{
account_info::AccountInfo,
program_error::ProgramError,
pubkey::Pubkey,
},
std::{
cell::RefMut,
mem::size_of,
},
};

/// This account stores the pubkeys that can execute administrative instructions in the Pyth
Expand Down Expand Up @@ -40,6 +47,9 @@ pub struct PermissionAccount {
}

impl PermissionAccount {
pub const MIN_SIZE_WITH_LAST_FEED_INDEX: usize =
size_of::<PermissionAccount>() + size_of::<u32>();

pub fn is_authorized(&self, key: &Pubkey, command: OracleCommand) -> bool {
#[allow(clippy::match_like_matches_macro)]
match (*key, command) {
Expand All @@ -50,9 +60,24 @@ impl PermissionAccount {
_ => false,
}
}

pub fn load_last_feed_index_mut<'a>(
account: &'a AccountInfo,
) -> Result<RefMut<'a, u32>, ProgramError> {
let start = size_of::<PermissionAccount>();
let end = start + size_of::<u32>();
assert_eq!(Self::MIN_SIZE_WITH_LAST_FEED_INDEX, end);
if account.data_len() < end {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(RefMut::map(account.try_borrow_mut_data()?, |data| {
bytemuck::from_bytes_mut(&mut data[start..end])
}))
}
}

impl PythAccount for PermissionAccount {
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS;
const INITIAL_SIZE: u32 = size_of::<PermissionAccount>() as u32;
const INITIAL_SIZE: u32 = Self::MIN_SIZE_WITH_LAST_FEED_INDEX as u32;
const NEW_ACCOUNT_SPACE: usize = Self::MIN_SIZE_WITH_LAST_FEED_INDEX;
}
5 changes: 3 additions & 2 deletions program/rust/src/accounts/price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ mod price_pythnet {
pub max_latency_: u8,
/// Various flags
pub flags: PriceAccountFlags,
/// Unused placeholder for alignment
pub unused_3_: i32,
/// Globally unique price feed index used for publishing.
/// Limited to 28 bites.
pub feed_index: u32,
/// Corresponding product account
pub product_account: Pubkey,
/// Next price account in the list
Expand Down
4 changes: 4 additions & 0 deletions program/rust/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ pub enum OracleError {
PermissionViolation = 619,
#[error("NeedsSuccesfulAggregation")]
NeedsSuccesfulAggregation = 620,
#[error("MaxLastFeedIndexReached")]
MaxLastFeedIndexReached = 621,
#[error("FeedIndexAlreadyInitialized")]
FeedIndexAlreadyInitialized = 622,
}

impl From<OracleError> for ProgramError {
Expand Down
6 changes: 6 additions & 0 deletions program/rust/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ pub enum OracleCommand {
// account[0] funding account [signer writable]
// account[1] price account [signer writable]
SetMaxLatency = 18,
/// Init price feed index
// account[0] funding account [signer writable]
// account[1] price account [writable]
// account[2] permissions account [writable]
// account[3] system program account []
InitPriceFeedIndex = 19,
}

#[repr(C)]
Expand Down
8 changes: 6 additions & 2 deletions program/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#![deny(warnings)]
// Allow non upper case globals from C
#![allow(non_upper_case_globals)]

Expand Down Expand Up @@ -29,6 +28,7 @@ mod log;
// While we have `pyth-sdk-rs` which exposes a more friendly interface, this is still useful when a
// downstream user wants to confirm for example that they can compile against the binary interface
// of this program for their specific solana version.
pub use crate::error::OracleError;
#[cfg(feature = "strum")]
pub use accounts::MessageType;
#[cfg(feature = "library")]
Expand All @@ -45,8 +45,12 @@ pub use accounts::{
PythAccount,
PythOracleSerialize,
};
#[cfg(feature = "library")]
pub use {
processor::find_publisher_index,
utils::get_status_for_conf_price_ratio,
};
use {
crate::error::OracleError,
processor::process_instruction,
solana_program::entrypoint,
};
Expand Down
43 changes: 43 additions & 0 deletions program/rust/src/processor.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
use {
crate::{
accounts::{
AccountHeader,
PermissionAccount,
},
deserialize::load_account_as_mut,
error::OracleError,
instruction::{
load_command_header_checked,
OracleCommand,
},
utils::{
pyth_assert,
try_convert,
},
},
solana_program::{
entrypoint::ProgramResult,
Expand All @@ -21,6 +30,7 @@ mod del_product;
mod del_publisher;
mod init_mapping;
mod init_price;
mod init_price_feed_index;
mod set_max_latency;
mod set_min_pub;
mod upd_permissions;
Expand All @@ -47,11 +57,20 @@ pub use {
upd_price::{
c_upd_aggregate,
c_upd_twap,
find_publisher_index,
upd_price,
upd_price_no_fail_on_error,
},
upd_product::upd_product,
};
use {
init_price_feed_index::init_price_feed_index,
solana_program::{
program_error::ProgramError,
rent::Rent,
sysvar::Sysvar,
},
};

/// Dispatch to the right instruction in the oracle.
pub fn process_instruction(
Expand Down Expand Up @@ -84,5 +103,29 @@ pub fn process_instruction(
DelProduct => del_product(program_id, accounts, instruction_data),
UpdPermissions => upd_permissions(program_id, accounts, instruction_data),
SetMaxLatency => set_max_latency(program_id, accounts, instruction_data),
InitPriceFeedIndex => init_price_feed_index(program_id, accounts, instruction_data),
}
}

fn reserve_new_price_feed_index(permissions_account: &AccountInfo) -> Result<u32, ProgramError> {
if permissions_account.data_len() < PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX {
let new_size = PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX;
let rent = Rent::get()?;
let new_minimum_balance = rent.minimum_balance(new_size);
pyth_assert(
permissions_account.lamports() >= new_minimum_balance,
ProgramError::AccountNotRentExempt,
)?;

permissions_account.realloc(new_size, true)?;
let mut header = load_account_as_mut::<AccountHeader>(permissions_account)?;
header.size = try_convert(new_size)?;
}
let mut last_feed_index = PermissionAccount::load_last_feed_index_mut(permissions_account)?;
*last_feed_index += 1;
pyth_assert(
*last_feed_index < (1 << 28),
OracleError::MaxLastFeedIndexReached.into(),
)?;
Ok(*last_feed_index)
}
13 changes: 9 additions & 4 deletions program/rust/src/processor/add_price.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use {
super::reserve_new_price_feed_index,
crate::{
accounts::{
PriceAccount,
Expand All @@ -18,6 +19,7 @@ use {
check_exponent_range,
check_permissioned_funding_account,
check_valid_funding_account,
check_valid_writable_account,
pyth_assert,
},
OracleError,
Expand All @@ -31,9 +33,10 @@ use {
};

/// Add new price account to a product account
// account[0] funding account [signer writable]
// account[1] product account [signer writable]
// account[2] new price account [signer writable]
// account[0] funding account [signer writable]
// account[1] product account [writable]
// account[2] new price account [writable]
// account[3] permissions account [writable]
pub fn add_price(
program_id: &Pubkey,
accounts: &[AccountInfo],
Expand All @@ -47,7 +50,6 @@ pub fn add_price(
ProgramError::InvalidArgument,
)?;


let (funding_account, product_account, price_account, permissions_account) = match accounts {
[x, y, z, p] => Ok((x, y, z, p)),
_ => Err(OracleError::InvalidNumberOfAccounts),
Expand All @@ -68,16 +70,19 @@ pub fn add_price(
permissions_account,
&cmd_args.header,
)?;
check_valid_writable_account(program_id, permissions_account)?;

let mut product_data =
load_checked::<ProductAccount>(product_account, cmd_args.header.version)?;

// TODO: where is it created???
let mut price_data = PriceAccount::initialize(price_account, cmd_args.header.version)?;
price_data.exponent = cmd_args.exponent;
price_data.price_type = cmd_args.price_type;
price_data.product_account = *product_account.key;
price_data.next_price_account = product_data.first_price_account;
price_data.min_pub_ = PRICE_ACCOUNT_DEFAULT_MIN_PUB;
price_data.feed_index = reserve_new_price_feed_index(permissions_account)?;
product_data.first_price_account = *price_account.key;

Ok(())
Expand Down
1 change: 0 additions & 1 deletion program/rust/src/processor/add_product.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ pub fn add_product(
hdr,
)?;


let mut mapping_data = load_checked::<MappingAccount>(tail_mapping_account, hdr.version)?;
// The mapping account must have free space to add the product account
pyth_assert(
Expand Down
1 change: 0 additions & 1 deletion program/rust/src/processor/del_product.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ pub fn del_product(
cmd_args,
)?;


{
let mut mapping_data = load_checked::<MappingAccount>(mapping_account, cmd_args.version)?;
let product_data = load_checked::<ProductAccount>(product_account, cmd_args.version)?;
Expand Down
1 change: 0 additions & 1 deletion program/rust/src/processor/init_price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ pub fn init_price(
&cmd_args.header,
)?;


let mut price_data = load_checked::<PriceAccount>(price_account, cmd_args.header.version)?;
pyth_assert(
price_data.price_type == cmd_args.price_type,
Expand Down
66 changes: 66 additions & 0 deletions program/rust/src/processor/init_price_feed_index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use {
super::reserve_new_price_feed_index,
crate::{
accounts::PriceAccount,
deserialize::{
load,
load_checked,
},
instruction::CommandHeader,
utils::{
check_permissioned_funding_account,
check_valid_funding_account,
check_valid_writable_account,
pyth_assert,
},
OracleError,
},
solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
program_error::ProgramError,
pubkey::Pubkey,
},
std::mem::size_of,
};

/// Init price feed index
// account[0] funding account [signer writable]
// account[1] price account [writable]
// account[2] permissions account [writable]
pub fn init_price_feed_index(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let cmd = load::<CommandHeader>(instruction_data)?;

pyth_assert(
instruction_data.len() == size_of::<CommandHeader>(),
ProgramError::InvalidArgument,
)?;

let (funding_account, price_account, permissions_account) = match accounts {
[x, y, p] => Ok((x, y, p)),
_ => Err(OracleError::InvalidNumberOfAccounts),
}?;

check_valid_funding_account(funding_account)?;
check_permissioned_funding_account(
program_id,
price_account,
funding_account,
permissions_account,
cmd,
)?;
check_valid_writable_account(program_id, permissions_account)?;

let mut price_account_data = load_checked::<PriceAccount>(price_account, cmd.version)?;
pyth_assert(
price_account_data.feed_index == 0,
OracleError::FeedIndexAlreadyInitialized.into(),
)?;
price_account_data.feed_index = reserve_new_price_feed_index(permissions_account)?;

Ok(())
}
1 change: 0 additions & 1 deletion program/rust/src/processor/set_min_pub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ pub fn set_min_pub(
&cmd.header,
)?;


let mut price_account_data = load_checked::<PriceAccount>(price_account, cmd.header.version)?;
price_account_data.min_pub_ = cmd.minimum_publishers;

Expand Down
1 change: 0 additions & 1 deletion program/rust/src/processor/upd_permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ pub fn upd_permissions(
OracleError::InvalidSystemAccount.into(),
)?;


// Create PermissionAccount if it doesn't exist
PermissionAccount::initialize_pda(
permissions_account,
Expand Down
Loading
Loading