Skip to content

Commit 11bd882

Browse files
Riatechetejasbadadareali-bahjati
authored
feat: resize mapping account (#419)
* feat: resize mapping account * fix: remove initPriceFeedIndex instr to reduce binary size * refactor: format * fix: remove explicit funding account from resizemapping ix * Apply suggestions from code review --------- Co-authored-by: Tejas Badadare <[email protected]> Co-authored-by: Ali Behjati <[email protected]>
1 parent e722345 commit 11bd882

13 files changed

+201
-159
lines changed

program/c/src/oracle/oracle.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ extern "C" {
2020
// various size constants
2121
#define PC_PUBKEY_SIZE 32
2222
#define PC_PUBKEY_SIZE_64 (PC_PUBKEY_SIZE/sizeof(uint64_t))
23-
#define PC_MAP_TABLE_SIZE 640
23+
#define PC_MAP_TABLE_SIZE 5000
2424

2525
// Total price component slots available
2626
#define PC_NUM_COMP_PYTHNET 128
@@ -117,7 +117,7 @@ typedef struct pc_map_table
117117
pc_pub_key_t prod_[PC_MAP_TABLE_SIZE]; // product accounts
118118
} pc_map_table_t;
119119

120-
static_assert( sizeof( pc_map_table_t ) == 20536, "" );
120+
static_assert( sizeof( pc_map_table_t ) == 160056, "" );
121121

122122
// variable length string
123123
typedef struct pc_str

program/rust/src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub enum OracleError {
5656
MaxLastFeedIndexReached = 621,
5757
#[error("FeedIndexAlreadyInitialized")]
5858
FeedIndexAlreadyInitialized = 622,
59+
#[error("NoNeedToResize")]
60+
NoNeedToResize = 623,
5961
}
6062

6163
impl From<OracleError> for ProgramError {

program/rust/src/instruction.rs

+2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ pub enum OracleCommand {
109109
// account[1] price account [writable]
110110
// account[2] permissions account [writable]
111111
InitPriceFeedIndex = 19,
112+
// account[0] mapping account [writable]
113+
ResizeMapping = 20,
112114
}
113115

114116
#[repr(C)]

program/rust/src/processor.rs

+15-10
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ mod del_product;
3232
mod del_publisher;
3333
mod init_mapping;
3434
mod init_price;
35-
mod init_price_feed_index;
35+
mod resize_mapping;
3636
mod set_max_latency;
3737
mod set_min_pub;
3838
mod upd_permissions;
@@ -44,6 +44,11 @@ pub use add_publisher::{
4444
DISABLE_ACCUMULATOR_V2,
4545
ENABLE_ACCUMULATOR_V2,
4646
};
47+
use solana_program::{
48+
program_error::ProgramError,
49+
rent::Rent,
50+
sysvar::Sysvar,
51+
};
4752
pub use {
4853
add_price::add_price,
4954
add_product::add_product,
@@ -53,6 +58,7 @@ pub use {
5358
del_publisher::del_publisher,
5459
init_mapping::init_mapping,
5560
init_price::init_price,
61+
resize_mapping::resize_mapping,
5662
set_max_latency::set_max_latency,
5763
set_min_pub::set_min_pub,
5864
upd_permissions::upd_permissions,
@@ -65,14 +71,7 @@ pub use {
6571
},
6672
upd_product::upd_product,
6773
};
68-
use {
69-
init_price_feed_index::init_price_feed_index,
70-
solana_program::{
71-
program_error::ProgramError,
72-
rent::Rent,
73-
sysvar::Sysvar,
74-
},
75-
};
74+
7675

7776
/// Dispatch to the right instruction in the oracle.
7877
pub fn process_instruction(
@@ -105,7 +104,13 @@ pub fn process_instruction(
105104
DelProduct => del_product(program_id, accounts, instruction_data),
106105
UpdPermissions => upd_permissions(program_id, accounts, instruction_data),
107106
SetMaxLatency => set_max_latency(program_id, accounts, instruction_data),
108-
InitPriceFeedIndex => init_price_feed_index(program_id, accounts, instruction_data),
107+
InitPriceFeedIndex => {
108+
solana_program::msg!(
109+
"Oracle init price feed index instruction has been removed. Bailing out!"
110+
);
111+
Err(OracleError::UnrecognizedInstruction.into())
112+
}
113+
ResizeMapping => resize_mapping(program_id, accounts, instruction_data),
109114
}
110115
}
111116

program/rust/src/processor/init_price_feed_index.rs

-66
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use {
2+
crate::{
3+
accounts::{
4+
AccountHeader,
5+
MappingAccount,
6+
PythAccount,
7+
},
8+
c_oracle_header::PC_MAGIC,
9+
deserialize::{
10+
load,
11+
load_account_as,
12+
},
13+
instruction::CommandHeader,
14+
utils::{
15+
check_valid_writable_account,
16+
pyth_assert,
17+
},
18+
OracleError,
19+
},
20+
solana_program::{
21+
account_info::AccountInfo,
22+
entrypoint::{
23+
ProgramResult,
24+
MAX_PERMITTED_DATA_INCREASE,
25+
},
26+
pubkey::Pubkey,
27+
},
28+
std::{
29+
cmp::min,
30+
mem::size_of,
31+
},
32+
};
33+
34+
/// Resize mapping account.
35+
// account[0] mapping account [writable]
36+
pub fn resize_mapping(
37+
program_id: &Pubkey,
38+
accounts: &[AccountInfo],
39+
instruction_data: &[u8],
40+
) -> ProgramResult {
41+
let mapping_account = match accounts {
42+
[x] => Ok(x),
43+
_ => Err(OracleError::InvalidNumberOfAccounts),
44+
}?;
45+
46+
let hdr = load::<CommandHeader>(instruction_data)?;
47+
48+
check_valid_writable_account(program_id, mapping_account)?;
49+
50+
{
51+
let account_header = load_account_as::<AccountHeader>(mapping_account)?;
52+
pyth_assert(
53+
account_header.magic_number == PC_MAGIC
54+
&& account_header.version == hdr.version
55+
&& account_header.account_type == MappingAccount::ACCOUNT_TYPE,
56+
OracleError::InvalidAccountHeader.into(),
57+
)?;
58+
}
59+
60+
pyth_assert(
61+
mapping_account.data_len() < size_of::<MappingAccount>(),
62+
OracleError::NoNeedToResize.into(),
63+
)?;
64+
let new_size = min(
65+
size_of::<MappingAccount>(),
66+
mapping_account.data_len() + MAX_PERMITTED_DATA_INCREASE,
67+
);
68+
mapping_account.realloc(new_size, true)?;
69+
70+
Ok(())
71+
}

program/rust/src/tests/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod test_message;
1717
mod test_permission_migration;
1818
mod test_publish;
1919
mod test_publish_batch;
20+
mod test_resize_mapping;
2021
mod test_set_max_latency;
2122
mod test_set_min_pub;
2223
mod test_sizes;

program/rust/src/tests/pyth_simulator.rs

+25
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,31 @@ impl PythSimulator {
478478
.map(|_| permissions_pubkey)
479479
}
480480

481+
pub async fn truncate_account(&mut self, key: Pubkey, size: usize) {
482+
let mut account = self.get_account(key).await.unwrap();
483+
account.data.truncate(size);
484+
self.context.set_account(&key, &account.into());
485+
}
486+
487+
pub async fn resize_mapping(
488+
&mut self,
489+
mapping_keypair: &Keypair,
490+
) -> Result<(), BanksClientError> {
491+
let cmd: CommandHeader = OracleCommand::ResizeMapping.into();
492+
let instruction = Instruction::new_with_bytes(
493+
self.program_id,
494+
bytes_of(&cmd),
495+
vec![AccountMeta::new(mapping_keypair.pubkey(), false)],
496+
);
497+
498+
self.process_ixs(
499+
&[instruction],
500+
&vec![],
501+
&copy_keypair(&self.genesis_keypair),
502+
)
503+
.await
504+
}
505+
481506
/// Get the account at `key`. Returns `None` if no such account exists.
482507
pub async fn get_account(&mut self, key: Pubkey) -> Option<Account> {
483508
self.context.banks_client.get_account(key).await.unwrap()

program/rust/src/tests/test_add_price.rs

-52
Original file line numberDiff line numberDiff line change
@@ -130,58 +130,6 @@ fn test_add_price() {
130130
assert!(product_data.first_price_account == *price_account_2.key);
131131
}
132132

133-
// Emulate pre-existing price accounts without a feed index.
134-
{
135-
let mut price_data = load_checked::<PriceAccount>(&price_account, PC_VERSION).unwrap();
136-
price_data.feed_index = 0;
137-
let mut price_data_2 = load_checked::<PriceAccount>(&price_account_2, PC_VERSION).unwrap();
138-
price_data_2.feed_index = 0;
139-
}
140-
let hdr_init_price_feed_index = CommandHeader::from(OracleCommand::InitPriceFeedIndex);
141-
let instruction_data_init_price_feed_index = bytes_of(&hdr_init_price_feed_index);
142-
process_instruction(
143-
&program_id,
144-
&[
145-
funding_account.clone(),
146-
price_account.clone(),
147-
permissions_account.clone(),
148-
],
149-
instruction_data_init_price_feed_index,
150-
)
151-
.unwrap();
152-
{
153-
let price_data = load_checked::<PriceAccount>(&price_account, PC_VERSION).unwrap();
154-
assert_eq!(price_data.feed_index, 3);
155-
}
156-
process_instruction(
157-
&program_id,
158-
&[
159-
funding_account.clone(),
160-
price_account_2.clone(),
161-
permissions_account.clone(),
162-
],
163-
instruction_data_init_price_feed_index,
164-
)
165-
.unwrap();
166-
{
167-
let price_data_2 = load_checked::<PriceAccount>(&price_account_2, PC_VERSION).unwrap();
168-
assert_eq!(price_data_2.feed_index, 4);
169-
}
170-
171-
// Feed index is already set.
172-
assert_eq!(
173-
process_instruction(
174-
&program_id,
175-
&[
176-
funding_account.clone(),
177-
price_account_2.clone(),
178-
permissions_account.clone(),
179-
],
180-
instruction_data_init_price_feed_index,
181-
),
182-
Err(OracleError::FeedIndexAlreadyInitialized.into())
183-
);
184-
185133
// Wrong number of accounts
186134
assert_eq!(
187135
process_instruction(

program/rust/src/tests/test_del_product.rs

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use {
22
crate::{
33
accounts::MappingAccount,
4+
deserialize::load,
45
tests::pyth_simulator::PythSimulator,
56
},
67
solana_program::pubkey::Pubkey,
@@ -41,32 +42,31 @@ async fn test_del_product() {
4142

4243
assert!(sim.get_account(product2.pubkey()).await.is_none());
4344

44-
let mapping_data = sim
45-
.get_account_data_as::<MappingAccount>(mapping_keypair.pubkey())
46-
.await
47-
.unwrap();
48-
assert!(mapping_product_list_equals(
49-
&mapping_data,
50-
vec![
51-
product1.pubkey(),
52-
product5.pubkey(),
53-
product3.pubkey(),
54-
product4.pubkey()
55-
]
56-
));
45+
{
46+
let mapping_account = sim.get_account(mapping_keypair.pubkey()).await.unwrap();
47+
let mapping_data = load::<MappingAccount>(&mapping_account.data).unwrap();
48+
assert!(mapping_product_list_equals(
49+
mapping_data,
50+
vec![
51+
product1.pubkey(),
52+
product5.pubkey(),
53+
product3.pubkey(),
54+
product4.pubkey()
55+
]
56+
));
57+
}
5758
assert!(sim.get_account(product5.pubkey()).await.is_some());
5859

5960

6061
assert!(sim.del_product(&mapping_keypair, &product4).await.is_ok());
61-
let mapping_data = sim
62-
.get_account_data_as::<MappingAccount>(mapping_keypair.pubkey())
63-
.await
64-
.unwrap();
65-
66-
assert!(mapping_product_list_equals(
67-
&mapping_data,
68-
vec![product1.pubkey(), product5.pubkey(), product3.pubkey()]
69-
));
62+
{
63+
let mapping_account = sim.get_account(mapping_keypair.pubkey()).await.unwrap();
64+
let mapping_data = load::<MappingAccount>(&mapping_account.data).unwrap();
65+
assert!(mapping_product_list_equals(
66+
mapping_data,
67+
vec![product1.pubkey(), product5.pubkey(), product3.pubkey()]
68+
));
69+
}
7070
}
7171

7272
/// Returns true if the list of products in `mapping_data` contains the keys in `expected` (in the

0 commit comments

Comments
 (0)