Skip to content

Bring back add_mapping instruction #418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion program/rust/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use {
},
};

mod add_mapping;
mod add_price;
mod add_product;
mod add_publisher;
Expand All @@ -45,6 +46,7 @@ pub use add_publisher::{
ENABLE_ACCUMULATOR_V2,
};
pub use {
add_mapping::add_mapping,
add_price::add_price,
add_product::add_product,
add_publisher::add_publisher,
Expand Down Expand Up @@ -84,7 +86,7 @@ pub fn process_instruction(

match load_command_header_checked(instruction_data)? {
InitMapping => init_mapping(program_id, accounts, instruction_data),
AddMapping => Err(OracleError::UnrecognizedInstruction.into()),
AddMapping => add_mapping(program_id, accounts, instruction_data),
AddProduct => add_product(program_id, accounts, instruction_data),
UpdProduct => upd_product(program_id, accounts, instruction_data),
AddPrice => add_price(program_id, accounts, instruction_data),
Expand Down
72 changes: 72 additions & 0 deletions program/rust/src/processor/add_mapping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use {
crate::{
accounts::{
MappingAccount,
PythAccount,
},
c_oracle_header::PC_MAP_TABLE_SIZE,
deserialize::{
load,
load_checked,
},
instruction::CommandHeader,
utils::{
check_valid_funding_account,
check_valid_signable_account_or_permissioned_funding_account,
pyth_assert,
},
OracleError,
},
solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
program_error::ProgramError,
pubkey::Pubkey,
},
};

/// Initialize and add new mapping account
// account[0] funding account [signer writable]
// account[1] tail mapping account [signer writable]
// account[2] new mapping account [signer writable]
pub fn add_mapping(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let (funding_account, cur_mapping, next_mapping, permissions_account_option) = match accounts {
[x, y, z] => Ok((x, y, z, None)),
[x, y, z, p] => Ok((x, y, z, Some(p))),
_ => Err(OracleError::InvalidNumberOfAccounts),
}?;

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

check_valid_funding_account(funding_account)?;
check_valid_signable_account_or_permissioned_funding_account(
program_id,
cur_mapping,
funding_account,
permissions_account_option,
hdr,
)?;
check_valid_signable_account_or_permissioned_funding_account(
program_id,
next_mapping,
funding_account,
permissions_account_option,
hdr,
)?;

let mut cur_mapping = load_checked::<MappingAccount>(cur_mapping, hdr.version)?;
pyth_assert(
cur_mapping.number_of_products == PC_MAP_TABLE_SIZE
&& cur_mapping.next_mapping_account == Pubkey::default(),
ProgramError::InvalidArgument,
)?;

MappingAccount::initialize(next_mapping, hdr.version)?;
cur_mapping.next_mapping_account = *next_mapping.key;

Ok(())
}
1 change: 1 addition & 0 deletions program/rust/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod pyth_simulator;
mod test_add_mapping;
mod test_add_price;
mod test_add_product;
mod test_add_publisher;
Expand Down
128 changes: 128 additions & 0 deletions program/rust/src/tests/test_add_mapping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use {
crate::{
accounts::{
clear_account,
MappingAccount,
PythAccount,
},
c_oracle_header::{
PC_MAGIC,
PC_MAP_TABLE_SIZE,
PC_VERSION,
},
deserialize::{
load_account_as_mut,
load_checked,
},
error::OracleError,
instruction::{
CommandHeader,
OracleCommand,
},
processor::process_instruction,
tests::test_utils::AccountSetup,
},
bytemuck::bytes_of,
solana_program::{
program_error::ProgramError,
pubkey::Pubkey,
},
};

#[test]
fn test_add_mapping() {
let hdr: CommandHeader = OracleCommand::AddMapping.into();
let instruction_data = bytes_of::<CommandHeader>(&hdr);

let program_id = Pubkey::new_unique();

let mut funding_setup = AccountSetup::new_funding();
let funding_account = funding_setup.as_account_info();

let mut curr_mapping_setup = AccountSetup::new::<MappingAccount>(&program_id);
let cur_mapping = curr_mapping_setup.as_account_info();
MappingAccount::initialize(&cur_mapping, PC_VERSION).unwrap();

let mut next_mapping_setup = AccountSetup::new::<MappingAccount>(&program_id);
let next_mapping = next_mapping_setup.as_account_info();

{
let mut cur_mapping_data =
load_checked::<MappingAccount>(&cur_mapping, PC_VERSION).unwrap();
cur_mapping_data.number_of_products = PC_MAP_TABLE_SIZE;
}

assert!(process_instruction(
&program_id,
&[
funding_account.clone(),
cur_mapping.clone(),
next_mapping.clone()
],
instruction_data
)
.is_ok());

{
let next_mapping_data = load_checked::<MappingAccount>(&next_mapping, PC_VERSION).unwrap();
let mut cur_mapping_data =
load_checked::<MappingAccount>(&cur_mapping, PC_VERSION).unwrap();

assert!(cur_mapping_data.next_mapping_account == *next_mapping.key);
assert!(next_mapping_data.next_mapping_account == Pubkey::default());
cur_mapping_data.next_mapping_account = Pubkey::default();
cur_mapping_data.number_of_products = 0;
}

clear_account(&next_mapping).unwrap();

assert_eq!(
process_instruction(
&program_id,
&[
funding_account.clone(),
cur_mapping.clone(),
next_mapping.clone()
],
instruction_data
),
Err(ProgramError::InvalidArgument)
);

{
let mut cur_mapping_data =
load_checked::<MappingAccount>(&cur_mapping, PC_VERSION).unwrap();
assert!(cur_mapping_data.next_mapping_account == Pubkey::default());
cur_mapping_data.number_of_products = PC_MAP_TABLE_SIZE;
cur_mapping_data.header.magic_number = 0;
}

assert_eq!(
process_instruction(
&program_id,
&[
funding_account.clone(),
cur_mapping.clone(),
next_mapping.clone()
],
instruction_data
),
Err(OracleError::InvalidAccountHeader.into())
);

{
let mut cur_mapping_data = load_account_as_mut::<MappingAccount>(&cur_mapping).unwrap();
cur_mapping_data.header.magic_number = PC_MAGIC;
}

assert!(process_instruction(
&program_id,
&[
funding_account.clone(),
cur_mapping.clone(),
next_mapping.clone()
],
instruction_data
)
.is_ok());
}
15 changes: 15 additions & 0 deletions program/rust/src/tests/test_permission_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use {
DelPublisherArgs,
InitPriceArgs,
OracleCommand::{
AddMapping,
AddPrice,
AddProduct,
AddPublisher,
Expand Down Expand Up @@ -116,6 +117,20 @@ fn test_permission_migration() {
)
.unwrap();

assert_eq!(
process_instruction(
&program_id,
&[
attacker_account.clone(),
mapping_account.clone(),
next_mapping_account.clone(),
permissions_account.clone()
],
bytes_of::<CommandHeader>(&AddMapping.into())
),
Err(OracleError::PermissionViolation.into())
);

assert_eq!(
process_instruction(
&program_id,
Expand Down
48 changes: 48 additions & 0 deletions program/rust/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ pub fn check_valid_funding_account(account: &AccountInfo) -> Result<(), ProgramE
)
}

pub fn valid_signable_account(
program_id: &Pubkey,
account: &AccountInfo,
) -> Result<bool, ProgramError> {
Ok(account.is_signer
&& account.is_writable
&& account.owner == program_id
&& get_rent()?.is_exempt(account.lamports(), account.data_len()))
}

pub fn check_valid_signable_account(
program_id: &Pubkey,
account: &AccountInfo,
) -> Result<(), ProgramError> {
pyth_assert(
valid_signable_account(program_id, account)?,
OracleError::InvalidSignableAccount.into(),
)
}

/// Check that `account` is a valid signable pyth account or
/// that `funding_account` is a signer and is permissioned by the `permission_account`
pub fn check_permissioned_funding_account(
Expand All @@ -80,6 +100,34 @@ pub fn check_permissioned_funding_account(
check_valid_writable_account(program_id, account)
}

/// Check that `account` is a valid signable pyth account or
/// that `funding_account` is a signer and is permissioned by the `permission_account`
pub fn check_valid_signable_account_or_permissioned_funding_account(
program_id: &Pubkey,
account: &AccountInfo,
funding_account: &AccountInfo,
permissions_account_option: Option<&AccountInfo>,
cmd_hdr: &CommandHeader,
) -> Result<(), ProgramError> {
if let Some(permissions_account) = permissions_account_option {
check_valid_permissions_account(program_id, permissions_account)?;
let permissions_account_data =
load_checked::<PermissionAccount>(permissions_account, cmd_hdr.version)?;
check_valid_funding_account(funding_account)?;
pyth_assert(
permissions_account_data.is_authorized(
funding_account.key,
OracleCommand::from_i32(cmd_hdr.command)
.ok_or(OracleError::UnrecognizedInstruction)?,
),
OracleError::PermissionViolation.into(),
)?;
check_valid_writable_account(program_id, account)
} else {
check_valid_signable_account(program_id, account)
}
}

/// Returns `true` if the `account` is fresh, i.e., its data can be overwritten.
/// Use this check to prevent accidentally overwriting accounts whose data is already populated.
pub fn valid_fresh_account(account: &AccountInfo) -> bool {
Expand Down
Loading