Skip to content

Commit 199c1b3

Browse files
jayantkJayant Krishnamurthyguibescos
authored
Migrate init_mapping to Rust (#219)
* v1 * stuff * fix * stuff * maybe? * whoops * ok * blah * more constants * ok * blah * imlement * maybe * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * ok * delete instruction from c * format * add lifetime * ok * does this work * reformat * test clippy * hm * cleanup * fix defines * Fix version of bytemuck and clean import * more pr comments * more pr commenst Co-authored-by: Jayant Krishnamurthy <jkrishnamurthy@jumptrading.com> Co-authored-by: Guillermo Bescos <gbescos@stanford.edu>
1 parent d8e3fa2 commit 199c1b3

File tree

6 files changed

+191
-8
lines changed

6 files changed

+191
-8
lines changed

program/c/src/oracle/oracle.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka )
538538
case e_cmd_upd_price:
539539
case e_cmd_agg_price: return upd_price( prm, ka );
540540
case e_cmd_upd_price_no_fail_on_error: return upd_price_no_fail_on_error( prm, ka );
541+
// init_mapping is overridden in Rust, but still implemented here to make the C unit tests pass.
541542
case e_cmd_init_mapping: return init_mapping( prm, ka );
542543
case e_cmd_add_mapping: return add_mapping( prm, ka );
543544
case e_cmd_add_product: return add_product( prm, ka );

program/rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ bindgen = "0.60.1"
1111
[dependencies]
1212
solana-program = "=1.10.29"
1313
borsh = "0.9"
14+
bytemuck = "1.11.0"
1415
thiserror = "1.0"
1516

16-
1717
[lib]
1818
crate-type = ["cdylib", "lib"]

program/rust/src/c_oracle_header.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,51 @@ use borsh::{
77
BorshDeserialize,
88
BorshSerialize,
99
};
10+
use bytemuck::{
11+
Pod,
12+
Zeroable,
13+
};
14+
1015
//bindings.rs is generated by build.rs to include
1116
//things defined in bindings.h
1217
include!("../bindings.rs");
18+
19+
#[cfg(target_endian = "little")]
20+
unsafe impl Zeroable for pc_acc {
21+
}
22+
23+
#[cfg(target_endian = "little")]
24+
unsafe impl Pod for pc_acc {
25+
}
26+
27+
#[cfg(target_endian = "little")]
28+
unsafe impl Zeroable for pc_map_table {
29+
}
30+
31+
#[cfg(target_endian = "little")]
32+
unsafe impl Pod for pc_map_table {
33+
}
34+
35+
#[cfg(target_endian = "little")]
36+
unsafe impl Zeroable for pc_prod {
37+
}
38+
39+
#[cfg(target_endian = "little")]
40+
unsafe impl Pod for pc_prod {
41+
}
42+
43+
#[cfg(target_endian = "little")]
44+
unsafe impl Zeroable for pc_price {
45+
}
46+
47+
#[cfg(target_endian = "little")]
48+
unsafe impl Pod for pc_price {
49+
}
50+
51+
#[cfg(target_endian = "little")]
52+
unsafe impl Zeroable for cmd_hdr {
53+
}
54+
55+
#[cfg(target_endian = "little")]
56+
unsafe impl Pod for cmd_hdr {
57+
}

program/rust/src/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
use solana_program::program_error::ProgramError;
33
use std::result::Result;
44
use thiserror::Error;
5+
56
// similar to ProgramResult but allows for multiple success values
67
pub type OracleResult = Result<u64, ProgramError>;
78

program/rust/src/processor.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
use std::mem::size_of;
2+
3+
use borsh::BorshDeserialize;
4+
use solana_program::program_error::ProgramError;
5+
use solana_program::pubkey::Pubkey;
6+
use solana_program::sysvar::slot_history::AccountInfo;
7+
18
use crate::c_entrypoint_wrapper;
29
use crate::c_oracle_header::{
310
cmd_hdr,
411
command_t_e_cmd_agg_price,
12+
command_t_e_cmd_init_mapping,
513
command_t_e_cmd_upd_account_version,
614
command_t_e_cmd_upd_price,
715
command_t_e_cmd_upd_price_no_fail_on_error,
@@ -12,13 +20,11 @@ use crate::error::{
1220
OracleResult,
1321
};
1422
use crate::rust_oracle::{
23+
init_mapping,
1524
update_price,
1625
update_version,
1726
};
18-
use ::std::mem::size_of;
19-
use borsh::BorshDeserialize;
20-
use solana_program::pubkey::Pubkey;
21-
use solana_program::sysvar::slot_history::AccountInfo;
27+
2228
///dispatch to the right instruction in the oracle
2329
pub fn process_instruction(
2430
program_id: &Pubkey,
@@ -27,13 +33,16 @@ pub fn process_instruction(
2733
input: *mut u8,
2834
) -> OracleResult {
2935
let cmd_hdr_size = size_of::<cmd_hdr>();
36+
if instruction_data.len() < cmd_hdr_size {
37+
return Err(ProgramError::InvalidArgument);
38+
}
3039
let cmd_data = cmd_hdr::try_from_slice(&instruction_data[..cmd_hdr_size])?;
3140

3241
if cmd_data.ver_ != PC_VERSION {
3342
//FIXME: I am not sure what's best to do here (this is copied from C)
3443
// it seems to me like we should not break when version numbers change
3544
//instead we should log a message that asks users to call update_version
36-
panic!("incorrect version numbers");
45+
return Err(ProgramError::InvalidArgument);
3746
}
3847

3948
match cmd_data
@@ -47,6 +56,7 @@ pub fn process_instruction(
4756
command_t_e_cmd_upd_account_version => {
4857
update_version(program_id, accounts, instruction_data)
4958
}
59+
command_t_e_cmd_init_mapping => init_mapping(program_id, accounts, instruction_data),
5060
_ => c_entrypoint_wrapper(input),
5161
}
5262
}

program/rust/src/rust_oracle.rs

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,36 @@
1-
use super::c_entrypoint_wrapper;
2-
use crate::error::OracleResult;
1+
use std::borrow::BorrowMut;
2+
use std::cell::{
3+
Ref,
4+
RefMut,
5+
};
6+
use std::mem::{
7+
size_of,
8+
size_of_val,
9+
};
10+
11+
use bytemuck::{
12+
try_from_bytes,
13+
try_from_bytes_mut,
14+
Pod,
15+
};
16+
use solana_program::entrypoint::SUCCESS;
17+
use solana_program::program_error::ProgramError;
18+
use solana_program::program_memory::sol_memset;
319
use solana_program::pubkey::Pubkey;
20+
use solana_program::rent::Rent;
421
use solana_program::sysvar::slot_history::AccountInfo;
522

23+
use crate::c_oracle_header::{
24+
cmd_hdr_t,
25+
pc_acc,
26+
pc_map_table_t,
27+
PC_ACCTYPE_MAPPING,
28+
PC_MAGIC,
29+
};
30+
use crate::error::OracleResult;
31+
32+
use super::c_entrypoint_wrapper;
33+
634
///Calls the c oracle update_price, and updates the Time Machine if needed
735
pub fn update_price(
836
_program_id: &Pubkey,
@@ -23,4 +51,102 @@ pub fn update_version(
2351
_instruction_data: &[u8],
2452
) -> OracleResult {
2553
panic!("Need to merge fix to pythd in order to implement this");
54+
// Ok(SUCCESS)
55+
}
56+
57+
58+
/// initialize the first mapping account in a new linked-list of mapping accounts
59+
/// accounts[0] funding account [signer writable]
60+
/// accounts[1] new mapping account [signer writable]
61+
pub fn init_mapping(
62+
program_id: &Pubkey,
63+
accounts: &[AccountInfo],
64+
instruction_data: &[u8],
65+
) -> OracleResult {
66+
let [_funding_account, fresh_mapping_account] = match accounts {
67+
[x, y]
68+
if valid_funding_account(x)
69+
&& valid_signable_account(program_id, y, size_of::<pc_map_table_t>())
70+
&& valid_fresh_account(y) =>
71+
{
72+
Ok([x, y])
73+
}
74+
_ => Err(ProgramError::InvalidArgument),
75+
}?;
76+
77+
// Initialize by setting to zero again (just in case) and populating the account header
78+
clear_account(fresh_mapping_account)?;
79+
80+
let hdr = load::<cmd_hdr_t>(instruction_data)?;
81+
let mut mapping_data = load_account_as_mut::<pc_map_table_t>(fresh_mapping_account)?;
82+
mapping_data.magic_ = PC_MAGIC;
83+
mapping_data.ver_ = hdr.ver_;
84+
mapping_data.type_ = PC_ACCTYPE_MAPPING;
85+
mapping_data.size_ = (size_of::<pc_map_table_t>() - size_of_val(&mapping_data.prod_)) as u32;
86+
87+
Ok(SUCCESS)
88+
}
89+
90+
fn valid_funding_account(account: &AccountInfo) -> bool {
91+
account.is_signer && account.is_writable
92+
}
93+
94+
fn valid_signable_account(program_id: &Pubkey, account: &AccountInfo, minimum_size: usize) -> bool {
95+
account.is_signer
96+
&& account.is_writable
97+
&& account.owner == program_id
98+
&& account.data_len() >= minimum_size
99+
&& Rent::default().is_exempt(account.lamports(), account.data_len())
100+
}
101+
102+
/// Returns `true` if the `account` is fresh, i.e., its data can be overwritten.
103+
/// Use this check to prevent accidentally overwriting accounts whose data is already populated.
104+
fn valid_fresh_account(account: &AccountInfo) -> bool {
105+
let pyth_acc = load_account_as::<pc_acc>(account);
106+
match pyth_acc {
107+
Ok(pyth_acc) => pyth_acc.magic_ == 0 && pyth_acc.ver_ == 0,
108+
Err(_) => false,
109+
}
110+
}
111+
112+
/// Sets the data of account to all-zero
113+
fn clear_account(account: &AccountInfo) -> Result<(), ProgramError> {
114+
let mut data = account
115+
.try_borrow_mut_data()
116+
.map_err(|_| ProgramError::InvalidArgument)?;
117+
let length = data.len();
118+
sol_memset(data.borrow_mut(), 0, length);
119+
Ok(())
120+
}
121+
122+
/// Interpret the bytes in `data` as a value of type `T`
123+
fn load<T: Pod>(data: &[u8]) -> Result<&T, ProgramError> {
124+
try_from_bytes(&data[0..size_of::<T>()]).map_err(|_| ProgramError::InvalidArgument)
125+
}
126+
127+
/// Interpret the bytes in `data` as a mutable value of type `T`
128+
#[allow(unused)]
129+
fn load_mut<T: Pod>(data: &mut [u8]) -> Result<&mut T, ProgramError> {
130+
try_from_bytes_mut(&mut data[0..size_of::<T>()]).map_err(|_| ProgramError::InvalidArgument)
131+
}
132+
133+
/// Get the data stored in `account` as a value of type `T`
134+
fn load_account_as<'a, T: Pod>(account: &'a AccountInfo) -> Result<Ref<'a, T>, ProgramError> {
135+
let data = account.try_borrow_data()?;
136+
137+
Ok(Ref::map(data, |data| {
138+
bytemuck::from_bytes(&data[0..size_of::<T>()])
139+
}))
140+
}
141+
142+
/// Mutably borrow the data in `account` as a value of type `T`.
143+
/// Any mutations to the returned value will be reflected in the account data.
144+
fn load_account_as_mut<'a, T: Pod>(
145+
account: &'a AccountInfo,
146+
) -> Result<RefMut<'a, T>, ProgramError> {
147+
let data = account.try_borrow_mut_data()?;
148+
149+
Ok(RefMut::map(data, |data| {
150+
bytemuck::from_bytes_mut(&mut data[0..size_of::<T>()])
151+
}))
26152
}

0 commit comments

Comments
 (0)