Skip to content

Commit 779362e

Browse files
Reisenguibescos
andauthored
Re-organize Modules (#300)
* style: Rust comment fixes and One grouping * chore: apply clippy to tests * Fix asserts clippy * Remove allow * style: Rust comment fixes and One grouping * chore: apply clippy to tests * refactor: condense account functionality * refactor: split processor * Cleanup lib.rs * Fix borrow errors * Cleanup * Fix comments Co-authored-by: Guillermo Bescos Alapont <gbescos@stanford.edu>
1 parent 8935a62 commit 779362e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1884
-1405
lines changed

program/rust/src/accounts.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
//! Account types and utilities for working with Pyth accounts.
2+
3+
use {
4+
crate::{
5+
c_oracle_header::PC_MAGIC,
6+
deserialize::load_account_as_mut,
7+
error::OracleError,
8+
utils::{
9+
check_valid_fresh_account,
10+
get_rent,
11+
pyth_assert,
12+
send_lamports,
13+
try_convert,
14+
},
15+
},
16+
bytemuck::{
17+
Pod,
18+
Zeroable,
19+
},
20+
solana_program::{
21+
account_info::AccountInfo,
22+
program::invoke_signed,
23+
program_error::ProgramError,
24+
program_memory::sol_memset,
25+
pubkey::Pubkey,
26+
system_instruction::{
27+
allocate,
28+
assign,
29+
},
30+
},
31+
std::{
32+
borrow::BorrowMut,
33+
cell::RefMut,
34+
mem::size_of,
35+
},
36+
};
37+
38+
mod mapping;
39+
mod permission;
40+
mod price;
41+
mod product;
42+
43+
pub use {
44+
mapping::MappingAccount,
45+
permission::{
46+
PermissionAccount,
47+
PERMISSIONS_SEED,
48+
},
49+
price::{
50+
PriceAccount,
51+
PriceComponent,
52+
PriceEma,
53+
PriceInfo,
54+
},
55+
product::ProductAccount,
56+
};
57+
58+
#[repr(C)]
59+
#[derive(Copy, Clone, Zeroable, Pod)]
60+
pub struct AccountHeader {
61+
pub magic_number: u32,
62+
pub version: u32,
63+
pub account_type: u32,
64+
pub size: u32,
65+
}
66+
67+
/// The PythAccount trait's purpose is to attach constants to the 3 types of accounts that Pyth has
68+
/// (mapping, price, product). This allows less duplicated code, because now we can create generic
69+
/// functions to perform common checks on the accounts and to load and initialize the accounts.
70+
pub trait PythAccount: Pod {
71+
/// `ACCOUNT_TYPE` is just the account discriminator, it is different for mapping, product and
72+
/// price
73+
const ACCOUNT_TYPE: u32;
74+
75+
/// `INITIAL_SIZE` is the value that the field `size_` will take when the account is first
76+
/// initialized this one is slightly tricky because for mapping (resp. price) `size_` won't
77+
/// include the unpopulated entries of `prod_` (resp. `comp_`). At the beginning there are 0
78+
/// products (resp. 0 components) therefore `INITIAL_SIZE` will be equal to the offset of
79+
/// `prod_` (resp. `comp_`) Similarly the product account `INITIAL_SIZE` won't include any
80+
/// key values.
81+
const INITIAL_SIZE: u32;
82+
83+
/// `minimum_size()` is the minimum size that the solana account holding the struct needs to
84+
/// have. `INITIAL_SIZE` <= `minimum_size()`
85+
const MINIMUM_SIZE: usize = size_of::<Self>();
86+
87+
/// Given an `AccountInfo`, verify it is sufficiently large and has the correct discriminator.
88+
fn initialize<'a>(
89+
account: &'a AccountInfo,
90+
version: u32,
91+
) -> Result<RefMut<'a, Self>, ProgramError> {
92+
pyth_assert(
93+
account.data_len() >= Self::MINIMUM_SIZE,
94+
OracleError::AccountTooSmall.into(),
95+
)?;
96+
97+
check_valid_fresh_account(account)?;
98+
clear_account(account)?;
99+
100+
{
101+
let mut account_header = load_account_as_mut::<AccountHeader>(account)?;
102+
account_header.magic_number = PC_MAGIC;
103+
account_header.version = version;
104+
account_header.account_type = Self::ACCOUNT_TYPE;
105+
account_header.size = Self::INITIAL_SIZE;
106+
}
107+
load_account_as_mut::<Self>(account)
108+
}
109+
110+
// Creates PDA accounts only when needed, and initializes it as one of the Pyth accounts
111+
fn initialize_pda<'a>(
112+
account: &AccountInfo<'a>,
113+
funding_account: &AccountInfo<'a>,
114+
system_program: &AccountInfo<'a>,
115+
program_id: &Pubkey,
116+
seeds: &[&[u8]],
117+
version: u32,
118+
) -> Result<(), ProgramError> {
119+
let target_rent = get_rent()?.minimum_balance(Self::MINIMUM_SIZE);
120+
121+
if account.lamports() < target_rent {
122+
send_lamports(
123+
funding_account,
124+
account,
125+
system_program,
126+
target_rent - account.lamports(),
127+
)?;
128+
}
129+
130+
if account.data_len() == 0 {
131+
allocate_data(account, system_program, Self::MINIMUM_SIZE, seeds)?;
132+
assign_owner(account, program_id, system_program, seeds)?;
133+
Self::initialize(account, version)?;
134+
}
135+
Ok(())
136+
}
137+
}
138+
139+
/// Given an already empty `AccountInfo`, allocate the data field to the given size. This make no
140+
/// assumptions about owner.
141+
fn allocate_data<'a>(
142+
account: &AccountInfo<'a>,
143+
system_program: &AccountInfo<'a>,
144+
space: usize,
145+
seeds: &[&[u8]],
146+
) -> Result<(), ProgramError> {
147+
let allocate_instruction = allocate(account.key, try_convert(space)?);
148+
invoke_signed(
149+
&allocate_instruction,
150+
&[account.clone(), system_program.clone()],
151+
&[seeds],
152+
)?;
153+
Ok(())
154+
}
155+
156+
/// Given a newly created `AccountInfo`, assign the owner to the given program id.
157+
fn assign_owner<'a>(
158+
account: &AccountInfo<'a>,
159+
owner: &Pubkey,
160+
system_program: &AccountInfo<'a>,
161+
seeds: &[&[u8]],
162+
) -> Result<(), ProgramError> {
163+
let assign_instruction = assign(account.key, owner);
164+
invoke_signed(
165+
&assign_instruction,
166+
&[account.clone(), system_program.clone()],
167+
&[seeds],
168+
)?;
169+
Ok(())
170+
}
171+
172+
/// Sets the data of account to all-zero
173+
pub fn clear_account(account: &AccountInfo) -> Result<(), ProgramError> {
174+
let mut data = account
175+
.try_borrow_mut_data()
176+
.map_err(|_| ProgramError::InvalidArgument)?;
177+
let length = data.len();
178+
sol_memset(data.borrow_mut(), 0, length);
179+
Ok(())
180+
}

program/rust/src/accounts/mapping.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use {
2+
super::{
3+
AccountHeader,
4+
PythAccount,
5+
},
6+
crate::c_oracle_header::{
7+
PC_ACCTYPE_MAPPING,
8+
PC_MAP_TABLE_SIZE,
9+
PC_MAP_TABLE_T_PROD_OFFSET,
10+
},
11+
bytemuck::{
12+
Pod,
13+
Zeroable,
14+
},
15+
solana_program::pubkey::Pubkey,
16+
};
17+
18+
#[repr(C)]
19+
#[derive(Copy, Clone)]
20+
pub struct MappingAccount {
21+
pub header: AccountHeader,
22+
pub number_of_products: u32,
23+
pub unused_: u32,
24+
pub next_mapping_account: Pubkey,
25+
pub products_list: [Pubkey; PC_MAP_TABLE_SIZE as usize],
26+
}
27+
28+
impl PythAccount for MappingAccount {
29+
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_MAPPING;
30+
/// Equal to the offset of `prod_` in `MappingAccount`, see the trait comment for more detail
31+
const INITIAL_SIZE: u32 = PC_MAP_TABLE_T_PROD_OFFSET as u32;
32+
}
33+
34+
// Unsafe impl because product_list is of size 640 and there's no derived trait for this size
35+
unsafe impl Pod for MappingAccount {
36+
}
37+
38+
unsafe impl Zeroable for MappingAccount {
39+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use {
2+
super::{
3+
AccountHeader,
4+
PythAccount,
5+
},
6+
crate::{
7+
c_oracle_header::PC_ACCTYPE_PERMISSIONS,
8+
instruction::OracleCommand,
9+
},
10+
bytemuck::{
11+
Pod,
12+
Zeroable,
13+
},
14+
solana_program::pubkey::Pubkey,
15+
std::mem::size_of,
16+
};
17+
18+
pub const PERMISSIONS_SEED: &str = "permissions";
19+
20+
/// This account stores the pubkeys that can execute administrative instructions in the Pyth
21+
/// program. Only the upgrade authority of the program can update these permissions.
22+
#[repr(C)]
23+
#[derive(Copy, Clone, Pod, Zeroable)]
24+
pub struct PermissionAccount {
25+
/// pyth account header
26+
pub header: AccountHeader,
27+
/// An authority that can do any administrative task
28+
pub master_authority: Pubkey,
29+
/// An authority that can :
30+
/// - Add mapping accounts
31+
/// - Add price accounts
32+
/// - Add product accounts
33+
/// - Delete price accounts
34+
/// - Delete product accounts
35+
/// - Update product accounts
36+
pub data_curation_authority: Pubkey,
37+
/// An authority that can :
38+
/// - Add publishers
39+
/// - Delete publishers
40+
/// - Set minimum number of publishers
41+
pub security_authority: Pubkey,
42+
}
43+
44+
impl PermissionAccount {
45+
pub fn is_authorized(&self, key: &Pubkey, command: OracleCommand) -> bool {
46+
#[allow(clippy::match_like_matches_macro)]
47+
match (*key, command) {
48+
(pubkey, OracleCommand::InitMapping) if pubkey == self.master_authority => true,
49+
_ => false,
50+
}
51+
}
52+
}
53+
54+
impl PythAccount for PermissionAccount {
55+
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS;
56+
const INITIAL_SIZE: u32 = size_of::<PermissionAccount>() as u32;
57+
}

program/rust/src/accounts/price.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use {
2+
super::{
3+
AccountHeader,
4+
PythAccount,
5+
},
6+
crate::c_oracle_header::{
7+
PC_ACCTYPE_PRICE,
8+
PC_COMP_SIZE,
9+
PC_PRICE_T_COMP_OFFSET,
10+
},
11+
bytemuck::{
12+
Pod,
13+
Zeroable,
14+
},
15+
solana_program::pubkey::Pubkey,
16+
};
17+
18+
#[repr(C)]
19+
#[derive(Copy, Clone, Pod, Zeroable)]
20+
pub struct PriceAccount {
21+
pub header: AccountHeader,
22+
/// Type of the price account
23+
pub price_type: u32,
24+
/// Exponent for the published prices
25+
pub exponent: i32,
26+
/// Current number of authorized publishers
27+
pub num_: u32,
28+
/// Number of valid quotes for the last aggregation
29+
pub num_qt_: u32,
30+
/// Last slot with a succesful aggregation (status : TRADING)
31+
pub last_slot_: u64,
32+
/// Second to last slot where aggregation was attempted
33+
pub valid_slot_: u64,
34+
/// Ema for price
35+
pub twap_: PriceEma,
36+
/// Ema for confidence
37+
pub twac_: PriceEma,
38+
/// Last time aggregation was attempted
39+
pub timestamp_: i64,
40+
/// Minimum valid publisher quotes for a succesful aggregation
41+
pub min_pub_: u8,
42+
pub unused_1_: i8,
43+
pub unused_2_: i16,
44+
pub unused_3_: i32,
45+
/// Corresponding product account
46+
pub product_account: Pubkey,
47+
/// Next price account in the list
48+
pub next_price_account: Pubkey,
49+
/// Second to last slot where aggregation was succesful (i.e. status : TRADING)
50+
pub prev_slot_: u64,
51+
/// Aggregate price at prev_slot_
52+
pub prev_price_: i64,
53+
/// Confidence interval at prev_slot_
54+
pub prev_conf_: u64,
55+
/// Timestamp of prev_slot_
56+
pub prev_timestamp_: i64,
57+
/// Last attempted aggregate results
58+
pub agg_: PriceInfo,
59+
/// Publishers' price components
60+
pub comp_: [PriceComponent; PC_COMP_SIZE as usize],
61+
}
62+
63+
#[repr(C)]
64+
#[derive(Copy, Clone, Pod, Zeroable)]
65+
pub struct PriceComponent {
66+
pub pub_: Pubkey,
67+
pub agg_: PriceInfo,
68+
pub latest_: PriceInfo,
69+
}
70+
71+
#[repr(C)]
72+
#[derive(Debug, Copy, Clone, Pod, Zeroable)]
73+
pub struct PriceInfo {
74+
pub price_: i64,
75+
pub conf_: u64,
76+
pub status_: u32,
77+
pub corp_act_status_: u32,
78+
pub pub_slot_: u64,
79+
}
80+
81+
#[repr(C)]
82+
#[derive(Debug, Copy, Clone, Pod, Zeroable)]
83+
pub struct PriceEma {
84+
pub val_: i64,
85+
pub numer_: i64,
86+
pub denom_: i64,
87+
}
88+
89+
impl PythAccount for PriceAccount {
90+
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PRICE;
91+
/// Equal to the offset of `comp_` in `PriceAccount`, see the trait comment for more detail
92+
const INITIAL_SIZE: u32 = PC_PRICE_T_COMP_OFFSET as u32;
93+
}

0 commit comments

Comments
 (0)