Skip to content

Commit 205a254

Browse files
authored
New interface with time machine (#230)
* added permissioned resize feature * set up the SMA tracking logic in send_data
1 parent 90e7769 commit 205a254

File tree

8 files changed

+292
-28
lines changed

8 files changed

+292
-28
lines changed

docker/Dockerfile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ RUN apt-get install -qq \
2727
qt5-qmake \
2828
qtbase5-dev-tools \
2929
libqt5websockets5-dev\
30-
libclang-dev
31-
30+
libclang-dev\
31+
python3-pip
32+
3233
# Install jcon-cpp library
3334
RUN git clone https://github.com/joncol/jcon-cpp.git /jcon-cpp && cd /jcon-cpp && git checkout 2235654e39c7af505d7158bf996e47e37a23d6e3 && mkdir build && cd build && cmake .. && make -j4 && make install
3435

@@ -56,6 +57,8 @@ export PYTHONPATH=\"\${PYTHONPATH:+\$PYTHONPATH:}\${HOME}/pyth-client\"\n\
5657

5758
COPY --chown=pyth:pyth . pyth-client/
5859

60+
RUN pip3 install solana
61+
5962
# Build off-chain binaries.
6063
RUN cd pyth-client && ./scripts/build.sh
6164

program/c/src/oracle/oracle.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,11 @@ typedef enum {
270270
// key[2] sysvar_clock account [readable]
271271
e_cmd_upd_price_no_fail_on_error,
272272

273-
// performs migation logic on the upgraded account. (resizes price accounts)
273+
// resizes a price account so that it fits the Time Machine
274274
// key[0] funding account [signer writable]
275-
// key[1] upgraded account [writable]
275+
// key[1] price account [Signer writable]
276276
// key[2] system program [readable]
277-
e_cmd_upd_account_version,
277+
e_cmd_resize_price_account,
278278
} command_t;
279279

280280
typedef struct cmd_hdr

program/rust/src/log.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ pub fn pre_log(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResu
8585
command_t_e_cmd_upd_product => {
8686
msg!("UpdateProduct");
8787
}
88+
89+
command_t_e_cmd_resize_price_account => {
90+
//accounts[1] is the updated account
91+
msg!("ResizePriceAccount: {}", accounts[1].key);
92+
}
8893
_ => {
8994
msg!("UnrecognizedInstruction");
9095
return Err(OracleError::UnrecognizedInstruction.into());

program/rust/src/processor.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use crate::c_oracle_header::{
1313
command_t_e_cmd_del_publisher,
1414
command_t_e_cmd_init_mapping,
1515
command_t_e_cmd_init_price,
16+
command_t_e_cmd_resize_price_account,
1617
command_t_e_cmd_set_min_pub,
17-
command_t_e_cmd_upd_account_version,
1818
command_t_e_cmd_upd_price,
1919
command_t_e_cmd_upd_price_no_fail_on_error,
2020
command_t_e_cmd_upd_product,
@@ -33,10 +33,10 @@ use crate::rust_oracle::{
3333
del_publisher,
3434
init_mapping,
3535
init_price,
36+
resize_price_account,
3637
set_min_pub,
3738
upd_product,
3839
update_price,
39-
update_version,
4040
};
4141

4242
///dispatch to the right instruction in the oracle
@@ -63,8 +63,8 @@ pub fn process_instruction(
6363
command_t_e_cmd_upd_price
6464
| command_t_e_cmd_upd_price_no_fail_on_error
6565
| command_t_e_cmd_agg_price => update_price(program_id, accounts, instruction_data, input),
66-
command_t_e_cmd_upd_account_version => {
67-
update_version(program_id, accounts, instruction_data)
66+
command_t_e_cmd_resize_price_account => {
67+
resize_price_account(program_id, accounts, instruction_data)
6868
}
6969
command_t_e_cmd_add_price => add_price(program_id, accounts, instruction_data),
7070
command_t_e_cmd_init_mapping => init_mapping(program_id, accounts, instruction_data),

program/rust/src/rust_oracle.rs

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ use solana_program::program_memory::{
1919
use solana_program::pubkey::Pubkey;
2020
use solana_program::rent::Rent;
2121

22+
23+
use crate::time_machine_types::PriceAccountWrapper;
24+
use solana_program::program::invoke;
25+
use solana_program::system_instruction::transfer;
26+
use solana_program::system_program::check_id;
27+
28+
2229
use crate::c_oracle_header::{
2330
cmd_add_price_t,
2431
cmd_add_publisher_t,
@@ -42,41 +49,121 @@ use crate::c_oracle_header::{
4249
PC_MAX_NUM_DECIMALS,
4350
PC_PROD_ACC_SIZE,
4451
PC_PTYPE_UNKNOWN,
52+
PC_VERSION,
53+
SUCCESSFULLY_UPDATED_AGGREGATE,
4554
};
4655
use crate::deserialize::{
4756
load,
4857
load_account_as,
4958
load_account_as_mut,
5059
};
5160
use crate::error::OracleResult;
52-
use crate::utils::pyth_assert;
5361
use crate::OracleError;
5462

63+
use crate::utils::pyth_assert;
64+
5565
use super::c_entrypoint_wrapper;
66+
const PRICE_T_SIZE: usize = size_of::<pc_price_t>();
67+
const PRICE_ACCOUNT_SIZE: usize = size_of::<PriceAccountWrapper>();
68+
5669

5770
///Calls the c oracle update_price, and updates the Time Machine if needed
5871
pub fn update_price(
5972
_program_id: &Pubkey,
60-
_accounts: &[AccountInfo],
73+
accounts: &[AccountInfo],
6174
_instruction_data: &[u8],
6275
input: *mut u8,
6376
) -> OracleResult {
64-
//For now, we did not change the behavior of this. this is just to show the proposed structure
65-
// of the program
66-
c_entrypoint_wrapper(input)
77+
let c_ret_value = c_entrypoint_wrapper(input)?;
78+
let price_account_info = &accounts[1];
79+
//accounts checks happen in c_entrypoint
80+
let account_len = price_account_info.try_data_len()?;
81+
match account_len {
82+
PRICE_T_SIZE => Ok(c_ret_value),
83+
PRICE_ACCOUNT_SIZE => {
84+
if c_ret_value == SUCCESSFULLY_UPDATED_AGGREGATE {
85+
let mut price_account =
86+
load_account_as_mut::<PriceAccountWrapper>(price_account_info)?;
87+
price_account.add_price_to_time_machine()?;
88+
}
89+
Ok(c_ret_value)
90+
}
91+
_ => Err(ProgramError::InvalidArgument),
92+
}
6793
}
68-
/// has version number/ account type dependant logic to make sure the given account is compatible
69-
/// with the current version
70-
/// updates the version number for all accounts, and resizes price accounts
71-
pub fn update_version(
72-
_program_id: &Pubkey,
73-
_accounts: &[AccountInfo],
94+
fn send_lamports<'a>(
95+
from: &AccountInfo<'a>,
96+
to: &AccountInfo<'a>,
97+
system_program: &AccountInfo<'a>,
98+
amount: u64,
99+
) -> Result<(), ProgramError> {
100+
let transfer_instruction = transfer(from.key, to.key, amount);
101+
invoke(
102+
&transfer_instruction,
103+
&[from.clone(), to.clone(), system_program.clone()],
104+
)?;
105+
Ok(())
106+
}
107+
108+
/// resizes a price account so that it fits the Time Machine
109+
/// key[0] funding account [signer writable]
110+
/// key[1] price account [Signer writable]
111+
/// key[2] system program [readable]
112+
pub fn resize_price_account(
113+
program_id: &Pubkey,
114+
accounts: &[AccountInfo],
74115
_instruction_data: &[u8],
75116
) -> OracleResult {
76-
panic!("Need to merge fix to pythd in order to implement this");
77-
// Ok(SUCCESS)
117+
let [funding_account_info, price_account_info, system_program] = match accounts {
118+
[x, y, z] => Ok([x, y, z]),
119+
_ => Err(ProgramError::InvalidArgument),
120+
}?;
121+
122+
check_valid_funding_account(funding_account_info)?;
123+
check_valid_signable_account(program_id, price_account_info, size_of::<pc_price_t>())?;
124+
pyth_assert(
125+
check_id(system_program.key),
126+
OracleError::InvalidSystemAccount.into(),
127+
)?;
128+
//throw an error if not a price account
129+
//need to makre sure it goes out of scope immediatly to avoid mutable borrow errors
130+
{
131+
load_checked::<pc_price_t>(price_account_info, PC_VERSION)?;
132+
}
133+
let account_len = price_account_info.try_data_len()?;
134+
match account_len {
135+
PRICE_T_SIZE => {
136+
//ensure account is still rent exempt after resizing
137+
let rent: Rent = Default::default();
138+
let lamports_needed: u64 = rent
139+
.minimum_balance(size_of::<PriceAccountWrapper>())
140+
.saturating_sub(price_account_info.lamports());
141+
if lamports_needed > 0 {
142+
send_lamports(
143+
funding_account_info,
144+
price_account_info,
145+
system_program,
146+
lamports_needed,
147+
)?;
148+
}
149+
//resize
150+
//we do not need to zero initialize since this is the first time this memory
151+
//is allocated
152+
price_account_info.realloc(size_of::<PriceAccountWrapper>(), false)?;
153+
//The load below would fail if the account was not a price account, reverting the whole
154+
// transaction
155+
let mut price_account =
156+
load_checked::<PriceAccountWrapper>(price_account_info, PC_VERSION)?;
157+
//Initialize Time Machine
158+
price_account.initialize_time_machine()?;
159+
Ok(SUCCESS)
160+
}
161+
PRICE_ACCOUNT_SIZE => Ok(SUCCESS),
162+
_ => Err(ProgramError::InvalidArgument),
163+
}
78164
}
79165

166+
80167
/// initialize the first mapping account in a new linked-list of mapping accounts
81168
/// accounts[0] funding account [signer writable]
82169
/// accounts[1] new mapping account [signer writable]
@@ -150,6 +237,7 @@ pub fn add_price(
150237
ProgramError::InvalidArgument,
151238
)?;
152239

240+
153241
let [funding_account, product_account, price_account] = match accounts {
154242
[x, y, z] => Ok([x, y, z]),
155243
_ => Err(ProgramError::InvalidArgument),

program/rust/src/time_machine_types.rs

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1-
#[derive(Debug, Clone)]
1+
use crate::c_oracle_header::{
2+
pc_price_t,
3+
PythAccount,
4+
EXTRA_PUBLISHER_SPACE,
5+
PC_ACCTYPE_PRICE,
6+
PC_PRICE_T_COMP_OFFSET,
7+
};
8+
use crate::error::OracleError;
9+
use bytemuck::{
10+
Pod,
11+
Zeroable,
12+
};
13+
use solana_program::msg;
14+
15+
16+
#[derive(Debug, Clone, Copy)]
217
#[repr(C)]
318
/// this wraps multiple SMA and tick trackers, and includes all the state
419
/// used by the time machine
@@ -7,10 +22,52 @@ pub struct TimeMachineWrapper {
722
place_holder: [u8; 1864],
823
}
924

25+
#[derive(Copy, Clone)]
26+
#[repr(C)]
27+
/// wraps everything stored in a price account
28+
pub struct PriceAccountWrapper {
29+
//an instance of the c price_t type
30+
pub price_data: pc_price_t,
31+
//space for more publishers
32+
pub extra_publisher_space: [u8; EXTRA_PUBLISHER_SPACE as usize],
33+
//TimeMachine
34+
pub time_machine: TimeMachineWrapper,
35+
}
36+
impl PriceAccountWrapper {
37+
pub fn initialize_time_machine(&mut self) -> Result<(), OracleError> {
38+
msg!("implement me");
39+
Ok(())
40+
}
41+
42+
pub fn add_price_to_time_machine(&mut self) -> Result<(), OracleError> {
43+
msg!("implement me");
44+
Ok(())
45+
}
46+
}
47+
48+
#[cfg(target_endian = "little")]
49+
unsafe impl Zeroable for PriceAccountWrapper {
50+
}
51+
52+
#[cfg(target_endian = "little")]
53+
unsafe impl Pod for PriceAccountWrapper {
54+
}
55+
56+
impl PythAccount for PriceAccountWrapper {
57+
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PRICE;
58+
const INITIAL_SIZE: u32 = PC_PRICE_T_COMP_OFFSET as u32;
59+
}
60+
1061
#[cfg(test)]
1162
pub mod tests {
12-
use crate::c_oracle_header::TIME_MACHINE_STRUCT_SIZE;
13-
use crate::time_machine_types::TimeMachineWrapper;
63+
use crate::c_oracle_header::{
64+
PRICE_ACCOUNT_SIZE,
65+
TIME_MACHINE_STRUCT_SIZE,
66+
};
67+
use crate::time_machine_types::{
68+
PriceAccountWrapper,
69+
TimeMachineWrapper,
70+
};
1471
use std::mem::size_of;
1572
#[test]
1673
///test that the size defined in C matches that
@@ -24,4 +81,15 @@ pub mod tests {
2481
size_of::<TimeMachineWrapper>()
2582
);
2683
}
84+
#[test]
85+
///test that priceAccountWrapper has a correct size
86+
fn c_price_account_size_is_correct() {
87+
assert_eq!(
88+
size_of::<PriceAccountWrapper>(),
89+
PRICE_ACCOUNT_SIZE.try_into().unwrap(),
90+
"expected PRICE_ACCOUNT_SIZE ({}) in oracle.h to the same as the size of PriceAccountWrapper ({})",
91+
PRICE_ACCOUNT_SIZE,
92+
size_of::<PriceAccountWrapper>()
93+
);
94+
}
2795
}

0 commit comments

Comments
 (0)