Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
180 changes: 180 additions & 0 deletions program/rust/src/accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//! Account types and utilities for working with Pyth accounts.

use {
crate::{
c_oracle_header::PC_MAGIC,
deserialize::load_account_as_mut,
error::OracleError,
utils::{
check_valid_fresh_account,
get_rent,
pyth_assert,
send_lamports,
try_convert,
},
},
bytemuck::{
Pod,
Zeroable,
},
solana_program::{
account_info::AccountInfo,
program::invoke_signed,
program_error::ProgramError,
program_memory::sol_memset,
pubkey::Pubkey,
system_instruction::{
allocate,
assign,
},
},
std::{
borrow::BorrowMut,
cell::RefMut,
mem::size_of,
},
};

mod mapping;
mod permission;
mod price;
mod product;

pub use {
mapping::MappingAccount,
permission::{
PermissionAccount,
PERMISSIONS_SEED,
},
price::{
PriceAccount,
PriceComponent,
PriceEma,
PriceInfo,
},
product::ProductAccount,
};

#[repr(C)]
#[derive(Copy, Clone, Zeroable, Pod)]
pub struct AccountHeader {
pub magic_number: u32,
pub version: u32,
pub account_type: u32,
pub size: u32,
}

/// The PythAccount trait's purpose is to attach constants to the 3 types of accounts that Pyth has
/// (mapping, price, product). This allows less duplicated code, because now we can create generic
/// functions to perform common checks on the accounts and to load and initialize the accounts.
pub trait PythAccount: Pod {
/// `ACCOUNT_TYPE` is just the account discriminator, it is different for mapping, product and
/// price
const ACCOUNT_TYPE: u32;

/// `INITIAL_SIZE` is the value that the field `size_` will take when the account is first
/// initialized this one is slightly tricky because for mapping (resp. price) `size_` won't
/// include the unpopulated entries of `prod_` (resp. `comp_`). At the beginning there are 0
/// products (resp. 0 components) therefore `INITIAL_SIZE` will be equal to the offset of
/// `prod_` (resp. `comp_`) Similarly the product account `INITIAL_SIZE` won't include any
/// key values.
const INITIAL_SIZE: u32;

/// `minimum_size()` is the minimum size that the solana account holding the struct needs to
/// have. `INITIAL_SIZE` <= `minimum_size()`
const MINIMUM_SIZE: usize = size_of::<Self>();

/// Given an `AccountInfo`, verify it is sufficiently large and has the correct discriminator.
fn initialize<'a>(
account: &'a AccountInfo,
version: u32,
) -> Result<RefMut<'a, Self>, ProgramError> {
pyth_assert(
account.data_len() >= Self::MINIMUM_SIZE,
OracleError::AccountTooSmall.into(),
)?;

check_valid_fresh_account(account)?;
clear_account(account)?;

{
let mut account_header = load_account_as_mut::<AccountHeader>(account)?;
account_header.magic_number = PC_MAGIC;
account_header.version = version;
account_header.account_type = Self::ACCOUNT_TYPE;
account_header.size = Self::INITIAL_SIZE;
}
load_account_as_mut::<Self>(account)
}

// Creates PDA accounts only when needed, and initializes it as one of the Pyth accounts
fn initialize_pda<'a>(
account: &AccountInfo<'a>,
funding_account: &AccountInfo<'a>,
system_program: &AccountInfo<'a>,
program_id: &Pubkey,
seeds: &[&[u8]],
version: u32,
) -> Result<(), ProgramError> {
let target_rent = get_rent()?.minimum_balance(Self::MINIMUM_SIZE);

if account.lamports() < target_rent {
send_lamports(
funding_account,
account,
system_program,
target_rent - account.lamports(),
)?;
}

if account.data_len() == 0 {
allocate_data(account, system_program, Self::MINIMUM_SIZE, seeds)?;
assign_owner(account, program_id, system_program, seeds)?;
Self::initialize(account, version)?;
}
Ok(())
}
}

/// Given an already empty `AccountInfo`, allocate the data field to the given size. This make no
/// assumptions about owner.
fn allocate_data<'a>(
account: &AccountInfo<'a>,
system_program: &AccountInfo<'a>,
space: usize,
seeds: &[&[u8]],
) -> Result<(), ProgramError> {
let allocate_instruction = allocate(account.key, try_convert(space)?);
invoke_signed(
&allocate_instruction,
&[account.clone(), system_program.clone()],
&[seeds],
)?;
Ok(())
}

/// Given a newly created `AccountInfo`, assign the owner to the given program id.
fn assign_owner<'a>(
account: &AccountInfo<'a>,
owner: &Pubkey,
system_program: &AccountInfo<'a>,
seeds: &[&[u8]],
) -> Result<(), ProgramError> {
let assign_instruction = assign(account.key, owner);
invoke_signed(
&assign_instruction,
&[account.clone(), system_program.clone()],
&[seeds],
)?;
Ok(())
}

/// Sets the data of account to all-zero
pub fn clear_account(account: &AccountInfo) -> Result<(), ProgramError> {
let mut data = account
.try_borrow_mut_data()
.map_err(|_| ProgramError::InvalidArgument)?;
let length = data.len();
sol_memset(data.borrow_mut(), 0, length);
Ok(())
}
39 changes: 39 additions & 0 deletions program/rust/src/accounts/mapping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use {
super::{
AccountHeader,
PythAccount,
},
crate::c_oracle_header::{
PC_ACCTYPE_MAPPING,
PC_MAP_TABLE_SIZE,
PC_MAP_TABLE_T_PROD_OFFSET,
},
bytemuck::{
Pod,
Zeroable,
},
solana_program::pubkey::Pubkey,
};

#[repr(C)]
#[derive(Copy, Clone)]
pub struct MappingAccount {
pub header: AccountHeader,
pub number_of_products: u32,
pub unused_: u32,
pub next_mapping_account: Pubkey,
pub products_list: [Pubkey; PC_MAP_TABLE_SIZE as usize],
}

impl PythAccount for MappingAccount {
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_MAPPING;
/// Equal to the offset of `prod_` in `MappingAccount`, see the trait comment for more detail
const INITIAL_SIZE: u32 = PC_MAP_TABLE_T_PROD_OFFSET as u32;
}

// Unsafe impl because product_list is of size 640 and there's no derived trait for this size
unsafe impl Pod for MappingAccount {
}

unsafe impl Zeroable for MappingAccount {
}
57 changes: 57 additions & 0 deletions program/rust/src/accounts/permission.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use {
super::{
AccountHeader,
PythAccount,
},
crate::{
c_oracle_header::PC_ACCTYPE_PERMISSIONS,
instruction::OracleCommand,
},
bytemuck::{
Pod,
Zeroable,
},
solana_program::pubkey::Pubkey,
std::mem::size_of,
};

pub const PERMISSIONS_SEED: &str = "permissions";

/// This account stores the pubkeys that can execute administrative instructions in the Pyth
/// program. Only the upgrade authority of the program can update these permissions.
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct PermissionAccount {
/// pyth account header
pub header: AccountHeader,
/// An authority that can do any administrative task
pub master_authority: Pubkey,
/// An authority that can :
/// - Add mapping accounts
/// - Add price accounts
/// - Add product accounts
/// - Delete price accounts
/// - Delete product accounts
/// - Update product accounts
pub data_curation_authority: Pubkey,
/// An authority that can :
/// - Add publishers
/// - Delete publishers
/// - Set minimum number of publishers
pub security_authority: Pubkey,
}

impl PermissionAccount {
pub fn is_authorized(&self, key: &Pubkey, command: OracleCommand) -> bool {
#[allow(clippy::match_like_matches_macro)]
match (*key, command) {
(pubkey, OracleCommand::InitMapping) if pubkey == self.master_authority => true,
_ => false,
}
}
}

impl PythAccount for PermissionAccount {
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS;
const INITIAL_SIZE: u32 = size_of::<PermissionAccount>() as u32;
}
93 changes: 93 additions & 0 deletions program/rust/src/accounts/price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use {
super::{
AccountHeader,
PythAccount,
},
crate::c_oracle_header::{
PC_ACCTYPE_PRICE,
PC_COMP_SIZE,
PC_PRICE_T_COMP_OFFSET,
},
bytemuck::{
Pod,
Zeroable,
},
solana_program::pubkey::Pubkey,
};

#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct PriceAccount {
pub header: AccountHeader,
/// Type of the price account
pub price_type: u32,
/// Exponent for the published prices
pub exponent: i32,
/// Current number of authorized publishers
pub num_: u32,
/// Number of valid quotes for the last aggregation
pub num_qt_: u32,
/// Last slot with a succesful aggregation (status : TRADING)
pub last_slot_: u64,
/// Second to last slot where aggregation was attempted
pub valid_slot_: u64,
/// Ema for price
pub twap_: PriceEma,
/// Ema for confidence
pub twac_: PriceEma,
/// Last time aggregation was attempted
pub timestamp_: i64,
/// Minimum valid publisher quotes for a succesful aggregation
pub min_pub_: u8,
pub unused_1_: i8,
pub unused_2_: i16,
pub unused_3_: i32,
/// Corresponding product account
pub product_account: Pubkey,
/// Next price account in the list
pub next_price_account: Pubkey,
/// Second to last slot where aggregation was succesful (i.e. status : TRADING)
pub prev_slot_: u64,
/// Aggregate price at prev_slot_
pub prev_price_: i64,
/// Confidence interval at prev_slot_
pub prev_conf_: u64,
/// Timestamp of prev_slot_
pub prev_timestamp_: i64,
/// Last attempted aggregate results
pub agg_: PriceInfo,
/// Publishers' price components
pub comp_: [PriceComponent; PC_COMP_SIZE as usize],
}

#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
pub struct PriceComponent {
pub pub_: Pubkey,
pub agg_: PriceInfo,
pub latest_: PriceInfo,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, Pod, Zeroable)]
pub struct PriceInfo {
pub price_: i64,
pub conf_: u64,
pub status_: u32,
pub corp_act_status_: u32,
pub pub_slot_: u64,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, Pod, Zeroable)]
pub struct PriceEma {
pub val_: i64,
pub numer_: i64,
pub denom_: i64,
}

impl PythAccount for PriceAccount {
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PRICE;
/// Equal to the offset of `comp_` in `PriceAccount`, see the trait comment for more detail
const INITIAL_SIZE: u32 = PC_PRICE_T_COMP_OFFSET as u32;
}
Loading