Skip to content

Commit 50a0e81

Browse files
authored
Rust instructions (#264)
* OracleCommand struct * Cleanup entrypoint: * Format * Cleanup * Additional comment * Delete accidental files * Explicit instruction order * Fix rust oracle
1 parent 5baf3c3 commit 50a0e81

File tree

8 files changed

+180
-71
lines changed

8 files changed

+180
-71
lines changed

program/rust/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ bindgen = "0.60.1"
1212
solana-program = "=1.10.29"
1313
bytemuck = "1.11.0"
1414
thiserror = "1.0"
15+
num-derive = "0.3"
16+
num-traits = "0.2"
1517

1618
[dev-dependencies]
1719
solana-program-test = "=1.10.29"

program/rust/src/deserialize.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::c_oracle_header::{
1111
PythAccount,
1212
PC_MAGIC,
1313
};
14+
use crate::error::OracleError;
1415
use crate::utils::{
1516
clear_account,
1617
pyth_assert,
@@ -23,22 +24,25 @@ use std::cell::{
2324
};
2425

2526
/// Interpret the bytes in `data` as a value of type `T`
26-
pub fn load<T: Pod>(data: &[u8]) -> Result<&T, ProgramError> {
27+
/// This will fail if :
28+
/// - `data` is too short
29+
/// - `data` is not aligned for T
30+
pub fn load<T: Pod>(data: &[u8]) -> Result<&T, OracleError> {
2731
try_from_bytes(
2832
data.get(0..size_of::<T>())
29-
.ok_or(ProgramError::InvalidArgument)?,
33+
.ok_or(OracleError::InstructionDataTooShort)?,
3034
)
31-
.map_err(|_| ProgramError::InvalidArgument)
35+
.map_err(|_| OracleError::InstructionDataSliceMisaligned)
3236
}
3337

3438
/// Interpret the bytes in `data` as a mutable value of type `T`
3539
#[allow(unused)]
36-
pub fn load_mut<T: Pod>(data: &mut [u8]) -> Result<&mut T, ProgramError> {
40+
pub fn load_mut<T: Pod>(data: &mut [u8]) -> Result<&mut T, OracleError> {
3741
try_from_bytes_mut(
3842
data.get_mut(0..size_of::<T>())
39-
.ok_or(ProgramError::InvalidArgument)?,
43+
.ok_or(OracleError::InstructionDataTooShort)?,
4044
)
41-
.map_err(|_| ProgramError::InvalidArgument)
45+
.map_err(|_| OracleError::InstructionDataSliceMisaligned)
4246
}
4347

4448
/// Get the data stored in `account` as a value of type `T`

program/rust/src/error.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,31 @@ use thiserror::Error;
77
pub enum OracleError {
88
/// Generic catch all error
99
#[error("Generic")]
10-
Generic = 600,
10+
Generic = 600,
1111
/// integer casting error
1212
#[error("IntegerCastingError")]
13-
IntegerCastingError = 601,
13+
IntegerCastingError = 601,
1414
/// c_entrypoint returned an unexpected value
1515
#[error("UnknownCError")]
16-
UnknownCError = 602,
16+
UnknownCError = 602,
1717
#[error("UnrecognizedInstruction")]
18-
UnrecognizedInstruction = 603,
18+
UnrecognizedInstruction = 603,
1919
#[error("InvalidFundingAccount")]
20-
InvalidFundingAccount = 604,
20+
InvalidFundingAccount = 604,
2121
#[error("InvalidSignableAccount")]
22-
InvalidSignableAccount = 605,
22+
InvalidSignableAccount = 605,
2323
#[error("InvalidSystemAccount")]
24-
InvalidSystemAccount = 606,
24+
InvalidSystemAccount = 606,
2525
#[error("InvalidWritableAccount")]
26-
InvalidWritableAccount = 607,
26+
InvalidWritableAccount = 607,
2727
#[error("InvalidFreshAccount")]
28-
InvalidFreshAccount = 608,
28+
InvalidFreshAccount = 608,
29+
#[error("InvalidInstructionVersion")]
30+
InvalidInstructionVersion = 609,
31+
#[error("InstructionDataTooShort")]
32+
InstructionDataTooShort = 610,
33+
#[error("InstructionDataSliceMisaligned")]
34+
InstructionDataSliceMisaligned = 611,
2935
}
3036

3137
impl From<OracleError> for ProgramError {

program/rust/src/instruction.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use crate::c_oracle_header::PC_VERSION;
2+
use crate::deserialize::load;
3+
use crate::error::OracleError;
4+
use bytemuck::{
5+
Pod,
6+
Zeroable,
7+
};
8+
use num_derive::{
9+
FromPrimitive,
10+
ToPrimitive,
11+
};
12+
use num_traits::FromPrimitive;
13+
14+
/// WARNING : NEW COMMANDS SHOULD BE ADDED AT THE END OF THE LIST
15+
#[repr(i32)]
16+
#[derive(FromPrimitive, ToPrimitive)]
17+
pub enum OracleCommand {
18+
// initialize first mapping list account
19+
// account[0] funding account [signer writable]
20+
// account[1] mapping account [signer writable]
21+
InitMapping = 0,
22+
// initialize and add new mapping account
23+
// account[0] funding account [signer writable]
24+
// account[1] tail mapping account [signer writable]
25+
// account[2] new mapping account [signer writable]
26+
AddMapping = 1,
27+
// initialize and add new product reference data account
28+
// account[0] funding account [signer writable]
29+
// account[1] mapping account [signer writable]
30+
// account[2] new product account [signer writable]
31+
AddProduct = 2,
32+
// update product account
33+
// account[0] funding account [signer writable]
34+
// account[1] product account [signer writable]
35+
UpdProduct = 3,
36+
// add new price account to a product account
37+
// account[0] funding account [signer writable]
38+
// account[1] product account [signer writable]
39+
// account[2] new price account [signer writable]
40+
AddPrice = 4,
41+
// add publisher to symbol account
42+
// account[0] funding account [signer writable]
43+
// account[1] price account [signer writable]
44+
AddPublisher = 5,
45+
// delete publisher from symbol account
46+
// account[0] funding account [signer writable]
47+
// account[1] price account [signer writable]
48+
DelPublisher = 6,
49+
// publish component price
50+
// account[0] funding account [signer writable]
51+
// account[1] price account [writable]
52+
// account[2] sysvar_clock account []
53+
UpdPrice = 7,
54+
// compute aggregate price
55+
// account[0] funding account [signer writable]
56+
// account[1] price account [writable]
57+
// account[2] sysvar_clock account []
58+
AggPrice = 8,
59+
// (re)initialize price account
60+
// account[0] funding account [signer writable]
61+
// account[1] new price account [signer writable]
62+
InitPrice = 9,
63+
// deprecated
64+
InitTest = 10,
65+
// deprecated
66+
UpdTest = 11,
67+
// set min publishers
68+
// account[0] funding account [signer writable]
69+
// account[1] price account [signer writable]
70+
SetMinPub = 12,
71+
// publish component price, never returning an error even if the update failed
72+
// account[0] funding account [signer writable]
73+
// account[1] price account [writable]
74+
// account[2] sysvar_clock account []
75+
UpdPriceNoFailOnError = 13,
76+
// resizes a price account so that it fits the Time Machine
77+
// account[0] funding account [signer writable]
78+
// account[1] price account [signer writable]
79+
// account[2] system program []
80+
ResizePriceAccount = 14,
81+
// deletes a price account
82+
// account[0] funding account [signer writable]
83+
// account[1] product account [signer writable]
84+
// account[2] price account [signer writable]
85+
DelPrice = 15,
86+
// deletes a product account
87+
// key[0] funding account [signer writable]
88+
// key[1] mapping account [signer writable]
89+
// key[2] product account [signer writable]
90+
DelProduct = 16,
91+
}
92+
93+
#[repr(C)]
94+
#[derive(Zeroable, Pod, Copy, Clone)]
95+
pub struct CommandHeader {
96+
pub version: u32,
97+
pub command: i32,
98+
}
99+
100+
pub fn load_command_header_checked(data: &[u8]) -> Result<OracleCommand, OracleError> {
101+
let command_header = load::<CommandHeader>(data)?;
102+
103+
if command_header.version != PC_VERSION {
104+
return Err(OracleError::InvalidInstructionVersion);
105+
}
106+
OracleCommand::from_i32(command_header.command).ok_or(OracleError::UnrecognizedInstruction)
107+
}

program/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
mod c_oracle_header;
88
mod deserialize;
99
mod error;
10+
mod instruction;
1011
mod processor;
1112
mod rust_oracle;
1213
mod time_machine_types;

program/rust/src/processor.rs

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
1-
use crate::c_oracle_header::{
2-
cmd_hdr,
3-
command_t_e_cmd_add_mapping,
4-
command_t_e_cmd_add_price,
5-
command_t_e_cmd_add_product,
6-
command_t_e_cmd_add_publisher,
7-
command_t_e_cmd_agg_price,
8-
command_t_e_cmd_del_price,
9-
command_t_e_cmd_del_product,
10-
command_t_e_cmd_del_publisher,
11-
command_t_e_cmd_init_mapping,
12-
command_t_e_cmd_init_price,
13-
command_t_e_cmd_resize_price_account,
14-
command_t_e_cmd_set_min_pub,
15-
command_t_e_cmd_upd_price,
16-
command_t_e_cmd_upd_price_no_fail_on_error,
17-
command_t_e_cmd_upd_product,
18-
PC_VERSION,
19-
};
20-
use crate::deserialize::load;
211
use crate::error::OracleError;
2+
use crate::instruction::{
3+
load_command_header_checked,
4+
OracleCommand,
5+
};
226
use solana_program::entrypoint::ProgramResult;
23-
use solana_program::program_error::ProgramError;
247
use solana_program::pubkey::Pubkey;
258
use solana_program::sysvar::slot_history::AccountInfo;
269

@@ -47,37 +30,27 @@ pub fn process_instruction(
4730
accounts: &[AccountInfo],
4831
instruction_data: &[u8],
4932
) -> ProgramResult {
50-
let cmd_data = load::<cmd_hdr>(instruction_data)?;
51-
52-
if cmd_data.ver_ != PC_VERSION {
53-
return Err(ProgramError::InvalidArgument);
54-
}
55-
56-
match cmd_data
57-
.cmd_
58-
.try_into()
59-
.map_err(|_| OracleError::IntegerCastingError)?
60-
{
61-
command_t_e_cmd_upd_price | command_t_e_cmd_agg_price => {
62-
upd_price(program_id, accounts, instruction_data)
63-
}
64-
command_t_e_cmd_upd_price_no_fail_on_error => {
33+
match load_command_header_checked(instruction_data)? {
34+
OracleCommand::InitMapping => init_mapping(program_id, accounts, instruction_data),
35+
OracleCommand::AddMapping => add_mapping(program_id, accounts, instruction_data),
36+
OracleCommand::AddProduct => add_product(program_id, accounts, instruction_data),
37+
OracleCommand::UpdProduct => upd_product(program_id, accounts, instruction_data),
38+
OracleCommand::AddPrice => add_price(program_id, accounts, instruction_data),
39+
OracleCommand::AddPublisher => add_publisher(program_id, accounts, instruction_data),
40+
OracleCommand::DelPublisher => del_publisher(program_id, accounts, instruction_data),
41+
OracleCommand::UpdPrice => upd_price(program_id, accounts, instruction_data),
42+
OracleCommand::AggPrice => upd_price(program_id, accounts, instruction_data),
43+
OracleCommand::InitPrice => init_price(program_id, accounts, instruction_data),
44+
OracleCommand::InitTest => Err(OracleError::UnrecognizedInstruction.into()),
45+
OracleCommand::UpdTest => Err(OracleError::UnrecognizedInstruction.into()),
46+
OracleCommand::SetMinPub => set_min_pub(program_id, accounts, instruction_data),
47+
OracleCommand::UpdPriceNoFailOnError => {
6548
upd_price_no_fail_on_error(program_id, accounts, instruction_data)
6649
}
67-
command_t_e_cmd_resize_price_account => {
50+
OracleCommand::ResizePriceAccount => {
6851
resize_price_account(program_id, accounts, instruction_data)
6952
}
70-
command_t_e_cmd_add_price => add_price(program_id, accounts, instruction_data),
71-
command_t_e_cmd_init_mapping => init_mapping(program_id, accounts, instruction_data),
72-
command_t_e_cmd_init_price => init_price(program_id, accounts, instruction_data),
73-
command_t_e_cmd_add_mapping => add_mapping(program_id, accounts, instruction_data),
74-
command_t_e_cmd_add_publisher => add_publisher(program_id, accounts, instruction_data),
75-
command_t_e_cmd_del_publisher => del_publisher(program_id, accounts, instruction_data),
76-
command_t_e_cmd_add_product => add_product(program_id, accounts, instruction_data),
77-
command_t_e_cmd_upd_product => upd_product(program_id, accounts, instruction_data),
78-
command_t_e_cmd_set_min_pub => set_min_pub(program_id, accounts, instruction_data),
79-
command_t_e_cmd_del_price => del_price(program_id, accounts, instruction_data),
80-
command_t_e_cmd_del_product => del_product(program_id, accounts, instruction_data),
81-
_ => Err(OracleError::UnrecognizedInstruction.into()),
53+
OracleCommand::DelPrice => del_price(program_id, accounts, instruction_data),
54+
OracleCommand::DelProduct => del_product(program_id, accounts, instruction_data),
8255
}
8356
}

program/rust/src/rust_oracle.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ use crate::c_oracle_header::{
4747
PC_VERSION,
4848
};
4949
use crate::deserialize::{
50-
initialize_pyth_account_checked, /* TODO: This has a confusingly similar name to a Solana
51-
* sdk function */
50+
initialize_pyth_account_checked,
5251
load,
5352
load_account_as_mut,
5453
load_checked,
5554
};
55+
use crate::instruction::CommandHeader;
5656
use crate::time_machine_types::PriceAccountWrapper;
5757
use crate::utils::{
5858
check_exponent_range,
@@ -729,9 +729,9 @@ pub fn del_product(
729729
check_valid_signable_account(program_id, product_account, PC_PROD_ACC_SIZE as usize)?;
730730

731731
{
732-
let cmd_args = load::<cmd_hdr_t>(instruction_data)?;
733-
let mut mapping_data = load_checked::<pc_map_table_t>(mapping_account, cmd_args.ver_)?;
734-
let product_data = load_checked::<pc_prod_t>(product_account, cmd_args.ver_)?;
732+
let cmd_args = load::<CommandHeader>(instruction_data)?;
733+
let mut mapping_data = load_checked::<pc_map_table_t>(mapping_account, cmd_args.version)?;
734+
let product_data = load_checked::<pc_prod_t>(product_account, cmd_args.version)?;
735735

736736
// This assertion is just to make the subtractions below simpler
737737
pyth_assert(mapping_data.num_ >= 1, ProgramError::InvalidArgument)?;

program/rust/src/tests/test_utils.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
use crate::c_oracle_header::PythAccount;
1+
use crate::c_oracle_header::{
2+
PythAccount,
3+
PC_VERSION,
4+
};
5+
use crate::instruction::{
6+
CommandHeader,
7+
OracleCommand,
8+
};
9+
use num_traits::ToPrimitive;
210
use solana_program::account_info::AccountInfo;
311
use solana_program::clock::Epoch;
412
use solana_program::native_token::LAMPORTS_PER_SOL;
@@ -13,7 +21,6 @@ use solana_program::{
1321
system_program,
1422
sysvar,
1523
};
16-
1724
const UPPER_BOUND_OF_ALL_ACCOUNT_SIZES: usize = 20536;
1825

1926
/// The goal of this struct is to easily instantiate fresh solana accounts
@@ -97,3 +104,12 @@ pub fn update_clock_slot(clock_account: &mut AccountInfo, slot: u64) {
97104
clock_data.slot = slot;
98105
clock_data.to_account_info(clock_account);
99106
}
107+
108+
impl Into<CommandHeader> for OracleCommand {
109+
fn into(self) -> CommandHeader {
110+
return CommandHeader {
111+
version: PC_VERSION,
112+
command: self.to_i32().unwrap(), // This can never fail and is only used in tests
113+
};
114+
}
115+
}

0 commit comments

Comments
 (0)