Skip to content

Commit b39b536

Browse files
jayantkJayant Krishnamurthy
andauthored
Implement add product instruction (#225)
* initial version * cleaning up stuff * add tests * delete c impl * pr comments * delete C test * one more Co-authored-by: Jayant Krishnamurthy <jkrishnamurthy@jumptrading.com>
1 parent f5e997b commit b39b536

File tree

8 files changed

+287
-142
lines changed

8 files changed

+287
-142
lines changed

program/c/src/oracle/oracle.c

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -59,48 +59,6 @@ static bool valid_writable_account( SolParameters *prm,
5959
is_rent_exempt( *ka->lamports, ka->data_len );
6060
}
6161

62-
static uint64_t add_product( SolParameters *prm, SolAccountInfo *ka )
63-
{
64-
// Account (1) is the mapping account that we're going to add to and
65-
// must be the tail (or last) mapping account in chain
66-
// Account (2) is the new product account
67-
// Verify that these are signed, writable accounts with correct ownership
68-
// and size
69-
if ( prm->ka_num != 3 ||
70-
!valid_funding_account( &ka[0] ) ||
71-
!valid_signable_account( prm, &ka[1], sizeof( pc_map_table_t ) ) ||
72-
!valid_signable_account( prm, &ka[2], PC_PROD_ACC_SIZE ) ) {
73-
return ERROR_INVALID_ARGUMENT;
74-
}
75-
76-
// Verify that mapping account is a valid tail account
77-
// that the new product account is uninitialized and that there is space
78-
// in the mapping account
79-
cmd_hdr_t *hdr = (cmd_hdr_t*)prm->data;
80-
pc_map_table_t *mptr = (pc_map_table_t*)ka[1].data;
81-
pc_prod_t *pptr = (pc_prod_t*)ka[2].data;
82-
if ( mptr->magic_ != PC_MAGIC ||
83-
mptr->ver_ != hdr->ver_ ||
84-
mptr->type_ != PC_ACCTYPE_MAPPING ||
85-
pptr->magic_ != 0 ||
86-
mptr->num_ >= PC_MAP_TABLE_SIZE ) {
87-
return ERROR_INVALID_ARGUMENT;
88-
}
89-
90-
// Initialize product account
91-
sol_memset( pptr, 0, PC_PROD_ACC_SIZE );
92-
pptr->magic_ = PC_MAGIC;
93-
pptr->ver_ = hdr->ver_;
94-
pptr->type_ = PC_ACCTYPE_PRODUCT;
95-
pptr->size_ = sizeof( pc_prod_t );
96-
97-
// finally add mapping account link
98-
pc_pub_key_assign( &mptr->prod_[mptr->num_++], (pc_pub_key_t*)ka[2].key );
99-
mptr->size_ = sizeof( pc_map_table_t ) - sizeof( mptr->prod_ ) +
100-
mptr->num_ * sizeof( pc_pub_key_t );
101-
return SUCCESS;
102-
}
103-
10462
#define PC_ADD_STR \
10563
tag = (pc_str_t*)src;\
10664
tag_len = 1 + tag->len_;\
@@ -471,10 +429,9 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka )
471429
case e_cmd_upd_price:
472430
case e_cmd_agg_price: return upd_price( prm, ka );
473431
case e_cmd_upd_price_no_fail_on_error: return upd_price_no_fail_on_error( prm, ka );
474-
// init_mapping is overridden in Rust, but still implemented here to make the C unit tests pass.
475432
case e_cmd_init_mapping: return ERROR_INVALID_ARGUMENT;
476433
case e_cmd_add_mapping: return ERROR_INVALID_ARGUMENT;
477-
case e_cmd_add_product: return add_product( prm, ka );
434+
case e_cmd_add_product: return ERROR_INVALID_ARGUMENT;
478435
case e_cmd_upd_product: return upd_product( prm, ka );
479436
case e_cmd_add_price: return add_price( prm, ka );
480437
case e_cmd_add_publisher: return add_publisher( prm, ka );

program/c/src/oracle/test_oracle.c

Lines changed: 0 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -7,101 +7,6 @@ uint64_t MAPPING_ACCOUNT_LAMPORTS = 143821440;
77
uint64_t PRODUCT_ACCOUNT_LAMPORTS = 4454400;
88
uint64_t PRICE_ACCOUNT_LAMPORTS = 23942400;
99

10-
Test(oracle, add_product) {
11-
// start with perfect inputs
12-
cmd_add_product_t idata = {
13-
.ver_ = PC_VERSION,
14-
.cmd_ = e_cmd_add_product,
15-
};
16-
SolPubkey p_id = {.x = { 0xff, }};
17-
SolPubkey pkey = {.x = { 1, }};
18-
SolPubkey mkey = {.x = { 2, }};
19-
SolPubkey skey = {.x = { 3, }};
20-
SolPubkey skey2 = {.x = { 4, }};
21-
uint64_t pqty = 100;
22-
pc_map_table_t mptr[1];
23-
sol_memset( mptr, 0, sizeof( pc_map_table_t ) );
24-
mptr->magic_ = PC_MAGIC;
25-
mptr->ver_ = PC_VERSION;
26-
mptr->type_ = PC_ACCTYPE_MAPPING;
27-
char sdata[PC_PROD_ACC_SIZE];
28-
pc_prod_t *sptr = (pc_prod_t*)sdata;
29-
sol_memset( sptr, 0, PC_PROD_ACC_SIZE );
30-
SolAccountInfo acc[] = {{
31-
.key = &pkey,
32-
.lamports = &pqty,
33-
.data_len = 0,
34-
.data = NULL,
35-
.owner = NULL,
36-
.rent_epoch = 0,
37-
.is_signer = true,
38-
.is_writable = true,
39-
.executable = false
40-
},{
41-
.key = &mkey,
42-
.lamports = &MAPPING_ACCOUNT_LAMPORTS,
43-
.data_len = sizeof( pc_map_table_t ),
44-
.data = (uint8_t*)mptr,
45-
.owner = &p_id,
46-
.rent_epoch = 0,
47-
.is_signer = true,
48-
.is_writable = true,
49-
.executable = false
50-
},{
51-
.key = &skey,
52-
.lamports = &PRODUCT_ACCOUNT_LAMPORTS,
53-
.data_len = PC_PROD_ACC_SIZE,
54-
.data = (uint8_t*)sptr,
55-
.owner = &p_id,
56-
.rent_epoch = 0,
57-
.is_signer = true,
58-
.is_writable = true,
59-
.executable = false
60-
}};
61-
SolParameters prm = {
62-
.ka = acc,
63-
.ka_num = 3,
64-
.data = (const uint8_t*)&idata,
65-
.data_len = sizeof( idata ),
66-
.program_id = &p_id
67-
};
68-
cr_assert( SUCCESS == dispatch( &prm, acc ) );
69-
cr_assert( sptr->magic_ == PC_MAGIC );
70-
cr_assert( sptr->ver_ == PC_VERSION );
71-
cr_assert( sptr->type_ == PC_ACCTYPE_PRODUCT );
72-
cr_assert( sptr->size_ == sizeof( pc_prod_t ) );
73-
cr_assert( mptr->num_ == 1 );
74-
cr_assert( pc_pub_key_equal( (pc_pub_key_t*)&skey, &mptr->prod_[0] ) );
75-
76-
// 2nd product
77-
acc[2].key = &skey2;
78-
sol_memset( sptr, 0, PC_PROD_ACC_SIZE );
79-
cr_assert( SUCCESS == dispatch( &prm, acc ) );
80-
cr_assert( mptr->num_ == 2 );
81-
cr_assert( pc_pub_key_equal( &mptr->prod_[1], (pc_pub_key_t*)&skey2 ) );
82-
83-
// invalid acc size
84-
acc[2].data_len = 1;
85-
cr_assert( ERROR_INVALID_ARGUMENT== dispatch( &prm, acc ) );
86-
acc[2].data_len = PC_PROD_ACC_SIZE;
87-
88-
// test fill up of mapping table
89-
sol_memset( mptr, 0, sizeof( pc_map_table_t ) );
90-
mptr->magic_ = PC_MAGIC;
91-
mptr->ver_ = PC_VERSION;
92-
mptr->type_ = PC_ACCTYPE_MAPPING;
93-
for( unsigned i = 0;; ++i ) {
94-
sol_memset( sptr, 0, PC_PROD_ACC_SIZE );
95-
uint64_t rc = dispatch( &prm, acc );
96-
if ( rc != SUCCESS ) {
97-
cr_assert( i == ( unsigned )(PC_MAP_TABLE_SIZE) );
98-
break;
99-
}
100-
cr_assert( mptr->num_ == i + 1 );
101-
cr_assert( rc == SUCCESS );
102-
}
103-
}
104-
10510
Test( oracle, add_publisher ) {
10611
// start with perfect inputs
10712
cmd_add_publisher_t idata = {

program/rust/src/processor.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::c_oracle_header::{
99
cmd_hdr,
1010
command_t_e_cmd_add_mapping,
1111
command_t_e_cmd_add_price,
12+
command_t_e_cmd_add_product,
1213
command_t_e_cmd_agg_price,
1314
command_t_e_cmd_init_mapping,
1415
command_t_e_cmd_upd_account_version,
@@ -23,6 +24,7 @@ use crate::error::{
2324
use crate::rust_oracle::{
2425
add_mapping,
2526
add_price,
27+
add_product,
2628
init_mapping,
2729
update_price,
2830
update_version,
@@ -63,6 +65,7 @@ pub fn process_instruction(
6365
command_t_e_cmd_add_price => add_price(program_id, accounts, instruction_data),
6466
command_t_e_cmd_init_mapping => init_mapping(program_id, accounts, instruction_data),
6567
command_t_e_cmd_add_mapping => add_mapping(program_id, accounts, instruction_data),
68+
command_t_e_cmd_add_product => add_product(program_id, accounts, instruction_data),
6669
_ => c_entrypoint_wrapper(input),
6770
}
6871
}

program/rust/src/rust_oracle.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use crate::c_oracle_header::{
3838
PC_PTYPE_UNKNOWN,
3939
};
4040
use crate::error::OracleResult;
41+
use crate::OracleError;
4142

4243
use crate::utils::pyth_assert;
4344

@@ -171,6 +172,47 @@ pub fn add_price(
171172
Ok(SUCCESS)
172173
}
173174

175+
pub fn add_product(
176+
program_id: &Pubkey,
177+
accounts: &[AccountInfo],
178+
instruction_data: &[u8],
179+
) -> OracleResult {
180+
let [_funding_account, tail_mapping_account, new_product_account] = match accounts {
181+
[x, y, z]
182+
if valid_funding_account(x)
183+
&& valid_signable_account(program_id, y, size_of::<pc_map_table_t>())
184+
&& valid_signable_account(program_id, z, PC_PROD_ACC_SIZE as usize)
185+
&& valid_fresh_account(z) =>
186+
{
187+
Ok([x, y, z])
188+
}
189+
_ => Err(ProgramError::InvalidArgument),
190+
}?;
191+
192+
let hdr = load::<cmd_hdr_t>(instruction_data)?;
193+
let mut mapping_data = load_mapping_account_mut(tail_mapping_account, hdr.ver_)?;
194+
// The mapping account must have free space to add the product account
195+
pyth_assert(
196+
mapping_data.num_ < PC_MAP_TABLE_SIZE,
197+
ProgramError::InvalidArgument,
198+
)?;
199+
200+
initialize_product_account(new_product_account, hdr.ver_)?;
201+
202+
let current_index: usize = try_convert(mapping_data.num_)?;
203+
unsafe {
204+
mapping_data.prod_[current_index]
205+
.k1_
206+
.copy_from_slice(&new_product_account.key.to_bytes())
207+
}
208+
mapping_data.num_ += 1;
209+
mapping_data.size_ =
210+
try_convert::<_, u32>(size_of::<pc_map_table_t>() - size_of_val(&mapping_data.prod_))?
211+
+ mapping_data.num_ * try_convert::<_, u32>(size_of::<pc_pub_key_t>())?;
212+
213+
Ok(SUCCESS)
214+
}
215+
174216
fn valid_funding_account(account: &AccountInfo) -> bool {
175217
account.is_signer && account.is_writable
176218
}
@@ -238,10 +280,24 @@ pub fn initialize_mapping_account(account: &AccountInfo, version: u32) -> Result
238280
Ok(())
239281
}
240282

283+
/// Initialize account as a new product account. This function will zero out any existing data in
284+
/// the account.
285+
pub fn initialize_product_account(account: &AccountInfo, version: u32) -> Result<(), ProgramError> {
286+
clear_account(account)?;
287+
288+
let mut prod_account = load_account_as_mut::<pc_prod_t>(account)?;
289+
prod_account.magic_ = PC_MAGIC;
290+
prod_account.ver_ = version;
291+
prod_account.type_ = PC_ACCTYPE_PRODUCT;
292+
prod_account.size_ = try_convert(size_of::<pc_prod_t>())?;
293+
294+
Ok(())
295+
}
296+
241297
/// Mutably borrow the data in `account` as a product account, validating that the account
242298
/// is properly formatted. Any mutations to the returned value will be reflected in the
243299
/// account data. Use this to read already-initialized accounts.
244-
fn load_product_account_mut<'a>(
300+
pub fn load_product_account_mut<'a>(
245301
account: &'a AccountInfo,
246302
expected_version: u32,
247303
) -> Result<RefMut<'a, pc_prod_t>, ProgramError> {
@@ -261,3 +317,9 @@ fn load_product_account_mut<'a>(
261317
pub fn pubkey_assign(target: &mut pc_pub_key_t, source: &[u8]) {
262318
unsafe { target.k1_.copy_from_slice(source) }
263319
}
320+
321+
/// Convert `x: T` into a `U`, returning the appropriate `OracleError` if the conversion fails.
322+
fn try_convert<T, U: TryFrom<T>>(x: T) -> Result<U, OracleError> {
323+
// Note: the error here assumes we're only applying this function to integers right now.
324+
U::try_from(x).map_err(|_| OracleError::IntegerCastingError)
325+
}

program/rust/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
mod test_add_mapping;
2+
mod test_add_product;
23
mod test_init_mapping;

program/rust/src/tests/test_add_mapping.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use crate::c_oracle_header::{
66
PC_MAP_TABLE_SIZE,
77
PC_VERSION,
88
};
9+
use crate::deserialize::load_account_as_mut;
910
use crate::rust_oracle::{
1011
add_mapping,
1112
clear_account,
1213
initialize_mapping_account,
13-
load_account_as_mut,
1414
load_mapping_account_mut,
1515
pubkey_assign,
1616
};

0 commit comments

Comments
 (0)