Skip to content

Commit

Permalink
feat: resize mapping account (#419)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people authored Jan 8, 2025
1 parent e722345 commit 11bd882
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 159 deletions.
4 changes: 2 additions & 2 deletions program/c/src/oracle/oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ extern "C" {
// various size constants
#define PC_PUBKEY_SIZE 32
#define PC_PUBKEY_SIZE_64 (PC_PUBKEY_SIZE/sizeof(uint64_t))
#define PC_MAP_TABLE_SIZE 640
#define PC_MAP_TABLE_SIZE 5000

// Total price component slots available
#define PC_NUM_COMP_PYTHNET 128
Expand Down Expand Up @@ -117,7 +117,7 @@ typedef struct pc_map_table
pc_pub_key_t prod_[PC_MAP_TABLE_SIZE]; // product accounts
} pc_map_table_t;

static_assert( sizeof( pc_map_table_t ) == 20536, "" );
static_assert( sizeof( pc_map_table_t ) == 160056, "" );

// variable length string
typedef struct pc_str
Expand Down
2 changes: 2 additions & 0 deletions program/rust/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ pub enum OracleError {
MaxLastFeedIndexReached = 621,
#[error("FeedIndexAlreadyInitialized")]
FeedIndexAlreadyInitialized = 622,
#[error("NoNeedToResize")]
NoNeedToResize = 623,
}

impl From<OracleError> for ProgramError {
Expand Down
2 changes: 2 additions & 0 deletions program/rust/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ pub enum OracleCommand {
// account[1] price account [writable]
// account[2] permissions account [writable]
InitPriceFeedIndex = 19,
// account[0] mapping account [writable]
ResizeMapping = 20,
}

#[repr(C)]
Expand Down
25 changes: 15 additions & 10 deletions program/rust/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ mod del_product;
mod del_publisher;
mod init_mapping;
mod init_price;
mod init_price_feed_index;
mod resize_mapping;
mod set_max_latency;
mod set_min_pub;
mod upd_permissions;
Expand All @@ -44,6 +44,11 @@ pub use add_publisher::{
DISABLE_ACCUMULATOR_V2,
ENABLE_ACCUMULATOR_V2,
};
use solana_program::{
program_error::ProgramError,
rent::Rent,
sysvar::Sysvar,
};
pub use {
add_price::add_price,
add_product::add_product,
Expand All @@ -53,6 +58,7 @@ pub use {
del_publisher::del_publisher,
init_mapping::init_mapping,
init_price::init_price,
resize_mapping::resize_mapping,
set_max_latency::set_max_latency,
set_min_pub::set_min_pub,
upd_permissions::upd_permissions,
Expand All @@ -65,14 +71,7 @@ pub use {
},
upd_product::upd_product,
};
use {
init_price_feed_index::init_price_feed_index,
solana_program::{
program_error::ProgramError,
rent::Rent,
sysvar::Sysvar,
},
};


/// Dispatch to the right instruction in the oracle.
pub fn process_instruction(
Expand Down Expand Up @@ -105,7 +104,13 @@ pub fn process_instruction(
DelProduct => del_product(program_id, accounts, instruction_data),
UpdPermissions => upd_permissions(program_id, accounts, instruction_data),
SetMaxLatency => set_max_latency(program_id, accounts, instruction_data),
InitPriceFeedIndex => init_price_feed_index(program_id, accounts, instruction_data),
InitPriceFeedIndex => {
solana_program::msg!(
"Oracle init price feed index instruction has been removed. Bailing out!"
);
Err(OracleError::UnrecognizedInstruction.into())
}
ResizeMapping => resize_mapping(program_id, accounts, instruction_data),
}
}

Expand Down
66 changes: 0 additions & 66 deletions program/rust/src/processor/init_price_feed_index.rs

This file was deleted.

71 changes: 71 additions & 0 deletions program/rust/src/processor/resize_mapping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use {
crate::{
accounts::{
AccountHeader,
MappingAccount,
PythAccount,
},
c_oracle_header::PC_MAGIC,
deserialize::{
load,
load_account_as,
},
instruction::CommandHeader,
utils::{
check_valid_writable_account,
pyth_assert,
},
OracleError,
},
solana_program::{
account_info::AccountInfo,
entrypoint::{
ProgramResult,
MAX_PERMITTED_DATA_INCREASE,
},
pubkey::Pubkey,
},
std::{
cmp::min,
mem::size_of,
},
};

/// Resize mapping account.
// account[0] mapping account [writable]
pub fn resize_mapping(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let mapping_account = match accounts {
[x] => Ok(x),
_ => Err(OracleError::InvalidNumberOfAccounts),
}?;

let hdr = load::<CommandHeader>(instruction_data)?;

check_valid_writable_account(program_id, mapping_account)?;

{
let account_header = load_account_as::<AccountHeader>(mapping_account)?;
pyth_assert(
account_header.magic_number == PC_MAGIC
&& account_header.version == hdr.version
&& account_header.account_type == MappingAccount::ACCOUNT_TYPE,
OracleError::InvalidAccountHeader.into(),
)?;
}

pyth_assert(
mapping_account.data_len() < size_of::<MappingAccount>(),
OracleError::NoNeedToResize.into(),
)?;
let new_size = min(
size_of::<MappingAccount>(),
mapping_account.data_len() + MAX_PERMITTED_DATA_INCREASE,
);
mapping_account.realloc(new_size, true)?;

Ok(())
}
1 change: 1 addition & 0 deletions program/rust/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod test_message;
mod test_permission_migration;
mod test_publish;
mod test_publish_batch;
mod test_resize_mapping;
mod test_set_max_latency;
mod test_set_min_pub;
mod test_sizes;
Expand Down
25 changes: 25 additions & 0 deletions program/rust/src/tests/pyth_simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,31 @@ impl PythSimulator {
.map(|_| permissions_pubkey)
}

pub async fn truncate_account(&mut self, key: Pubkey, size: usize) {
let mut account = self.get_account(key).await.unwrap();
account.data.truncate(size);
self.context.set_account(&key, &account.into());
}

pub async fn resize_mapping(
&mut self,
mapping_keypair: &Keypair,
) -> Result<(), BanksClientError> {
let cmd: CommandHeader = OracleCommand::ResizeMapping.into();
let instruction = Instruction::new_with_bytes(
self.program_id,
bytes_of(&cmd),
vec![AccountMeta::new(mapping_keypair.pubkey(), false)],
);

self.process_ixs(
&[instruction],
&vec![],
&copy_keypair(&self.genesis_keypair),
)
.await
}

/// Get the account at `key`. Returns `None` if no such account exists.
pub async fn get_account(&mut self, key: Pubkey) -> Option<Account> {
self.context.banks_client.get_account(key).await.unwrap()
Expand Down
52 changes: 0 additions & 52 deletions program/rust/src/tests/test_add_price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,58 +130,6 @@ fn test_add_price() {
assert!(product_data.first_price_account == *price_account_2.key);
}

// Emulate pre-existing price accounts without a feed index.
{
let mut price_data = load_checked::<PriceAccount>(&price_account, PC_VERSION).unwrap();
price_data.feed_index = 0;
let mut price_data_2 = load_checked::<PriceAccount>(&price_account_2, PC_VERSION).unwrap();
price_data_2.feed_index = 0;
}
let hdr_init_price_feed_index = CommandHeader::from(OracleCommand::InitPriceFeedIndex);
let instruction_data_init_price_feed_index = bytes_of(&hdr_init_price_feed_index);
process_instruction(
&program_id,
&[
funding_account.clone(),
price_account.clone(),
permissions_account.clone(),
],
instruction_data_init_price_feed_index,
)
.unwrap();
{
let price_data = load_checked::<PriceAccount>(&price_account, PC_VERSION).unwrap();
assert_eq!(price_data.feed_index, 3);
}
process_instruction(
&program_id,
&[
funding_account.clone(),
price_account_2.clone(),
permissions_account.clone(),
],
instruction_data_init_price_feed_index,
)
.unwrap();
{
let price_data_2 = load_checked::<PriceAccount>(&price_account_2, PC_VERSION).unwrap();
assert_eq!(price_data_2.feed_index, 4);
}

// Feed index is already set.
assert_eq!(
process_instruction(
&program_id,
&[
funding_account.clone(),
price_account_2.clone(),
permissions_account.clone(),
],
instruction_data_init_price_feed_index,
),
Err(OracleError::FeedIndexAlreadyInitialized.into())
);

// Wrong number of accounts
assert_eq!(
process_instruction(
Expand Down
44 changes: 22 additions & 22 deletions program/rust/src/tests/test_del_product.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
crate::{
accounts::MappingAccount,
deserialize::load,
tests::pyth_simulator::PythSimulator,
},
solana_program::pubkey::Pubkey,
Expand Down Expand Up @@ -41,32 +42,31 @@ async fn test_del_product() {

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

let mapping_data = sim
.get_account_data_as::<MappingAccount>(mapping_keypair.pubkey())
.await
.unwrap();
assert!(mapping_product_list_equals(
&mapping_data,
vec![
product1.pubkey(),
product5.pubkey(),
product3.pubkey(),
product4.pubkey()
]
));
{
let mapping_account = sim.get_account(mapping_keypair.pubkey()).await.unwrap();
let mapping_data = load::<MappingAccount>(&mapping_account.data).unwrap();
assert!(mapping_product_list_equals(
mapping_data,
vec![
product1.pubkey(),
product5.pubkey(),
product3.pubkey(),
product4.pubkey()
]
));
}
assert!(sim.get_account(product5.pubkey()).await.is_some());


assert!(sim.del_product(&mapping_keypair, &product4).await.is_ok());
let mapping_data = sim
.get_account_data_as::<MappingAccount>(mapping_keypair.pubkey())
.await
.unwrap();

assert!(mapping_product_list_equals(
&mapping_data,
vec![product1.pubkey(), product5.pubkey(), product3.pubkey()]
));
{
let mapping_account = sim.get_account(mapping_keypair.pubkey()).await.unwrap();
let mapping_data = load::<MappingAccount>(&mapping_account.data).unwrap();
assert!(mapping_product_list_equals(
mapping_data,
vec![product1.pubkey(), product5.pubkey(), product3.pubkey()]
));
}
}

/// Returns true if the list of products in `mapping_data` contains the keys in `expected` (in the
Expand Down
Loading

0 comments on commit 11bd882

Please sign in to comment.