Skip to content

Commit 32b0013

Browse files
authored
Init permissions (#275)
* Checkpoint * Compiles * Start pyth simulator * Working implementation of test bench with upgrade_authority * Test works * Renames * Cleanup * Cleanup imports * Comments * Fix typo * Fix some feedbakc * Add permission check * Fixing feedback * More feeback * Stop storing bump * Fix 1 lamport attack * Extend tests * Cleanup pyth simulator * Check accpnt with previous balance * Merge * Delete accidental file * Make some fields private * Payer as argument * Rename check authority
1 parent e08e76b commit 32b0013

File tree

14 files changed

+610
-58
lines changed

14 files changed

+610
-58
lines changed

program/c/src/oracle/oracle.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const uint64_t EXTRA_PUBLISHER_SPACE = 1000ULL;
5151
#define PC_ACCTYPE_PRODUCT 2
5252
#define PC_ACCTYPE_PRICE 3
5353
#define PC_ACCTYPE_TEST 4
54+
#define PC_ACCTYPE_PERMISSIONS 5
5455

5556
// binary version of sysvar_clock account id
5657
const uint64_t sysvar_clock[] = {

program/rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ bytemuck = "1.11.0"
1414
thiserror = "1.0"
1515
num-derive = "0.3"
1616
num-traits = "0.2"
17+
bincode = "1.3.3"
1718

1819
[dev-dependencies]
1920
solana-program-test = "=1.10.29"

program/rust/src/c_oracle_header.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use std::mem::size_of;
1313
//things defined in bindings.h
1414
include!("../bindings.rs");
1515

16+
pub const PERMISSIONS_SEED: &str = "permissions";
17+
1618

1719
/// If ci > price / PC_MAX_CI_DIVISOR, set publisher status to unknown.
1820
/// (e.g., 20 means ci must be < 5% of price)
@@ -58,6 +60,35 @@ impl PythAccount for PriceAccount {
5860
const INITIAL_SIZE: u32 = PC_PRICE_T_COMP_OFFSET as u32;
5961
}
6062

63+
impl PythAccount for PermissionAccount {
64+
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS;
65+
const INITIAL_SIZE: u32 = size_of::<PermissionAccount>() as u32;
66+
}
67+
68+
/// This account stores the pubkeys that can execute administrative instructions in the Pyth
69+
/// program. Only the upgrade authority of the program can update these permissions.
70+
#[repr(C)]
71+
#[derive(Copy, Clone, Pod, Zeroable)]
72+
pub struct PermissionAccount {
73+
/// pyth account header
74+
pub header: AccountHeader,
75+
/// An authority that can do any administrative task
76+
pub master_authority: Pubkey,
77+
/// An authority that can :
78+
/// - Add mapping accounts
79+
/// - Add price accounts
80+
/// - Add product accounts
81+
/// - Delete price accounts
82+
/// - Delete product accounts
83+
/// - Update product accounts
84+
pub data_curation_authority: Pubkey,
85+
/// An authority that can :
86+
/// - Add publishers
87+
/// - Delete publishers
88+
/// - Set minimum number of publishers
89+
pub security_authority: Pubkey,
90+
}
91+
6192
#[repr(C)]
6293
#[derive(Copy, Clone, Pod, Zeroable)]
6394
pub struct PriceAccount {

program/rust/src/deserialize.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use bytemuck::{
55
try_from_bytes_mut,
66
Pod,
77
};
8+
use solana_program::pubkey::Pubkey;
9+
use solana_program::rent::Rent;
810

911
use crate::c_oracle_header::{
1012
AccountHeader,
@@ -13,9 +15,12 @@ use crate::c_oracle_header::{
1315
};
1416
use crate::error::OracleError;
1517
use crate::utils::{
18+
allocate_data,
19+
assign_owner,
1620
check_valid_fresh_account,
1721
clear_account,
1822
pyth_assert,
23+
send_lamports,
1924
};
2025
use solana_program::account_info::AccountInfo;
2126
use solana_program::program_error::ProgramError;
@@ -112,3 +117,29 @@ pub fn initialize_pyth_account_checked<'a, T: PythAccount>(
112117

113118
load_account_as_mut::<T>(account)
114119
}
120+
121+
// Creates pda if needed and initializes it as one of the Pyth accounts
122+
pub fn create_pda_if_needed<'a, T: PythAccount>(
123+
account: &AccountInfo<'a>,
124+
funding_account: &AccountInfo<'a>,
125+
system_program: &AccountInfo<'a>,
126+
program_id: &Pubkey,
127+
seeds: &[&[u8]],
128+
version: u32,
129+
) -> Result<(), ProgramError> {
130+
let target_rent = Rent::default().minimum_balance(T::MINIMUM_SIZE);
131+
if account.lamports() < target_rent {
132+
send_lamports(
133+
funding_account,
134+
account,
135+
system_program,
136+
target_rent - account.lamports(),
137+
)?;
138+
}
139+
if account.data_len() == 0 {
140+
allocate_data(account, system_program, T::MINIMUM_SIZE, seeds)?;
141+
assign_owner(account, program_id, system_program, seeds)?;
142+
initialize_pyth_account_checked::<T>(account, version)?;
143+
}
144+
Ok(())
145+
}

program/rust/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ pub enum OracleError {
3434
InstructionDataSliceMisaligned = 611,
3535
#[error("AccountTooSmall")]
3636
AccountTooSmall = 612,
37+
#[error("DeserializationError")]
38+
DeserializationError = 613,
39+
#[error("FailedAuthenticatingUpgradeAuthority")]
40+
InvalidUpgradeAuthority = 614,
41+
#[error("FailedPdaVerification")]
42+
InvalidPda = 615,
3743
}
3844

3945
impl From<OracleError> for ProgramError {

program/rust/src/instruction.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ pub enum OracleCommand {
8989
// key[1] mapping account [signer writable]
9090
// key[2] product account [signer writable]
9191
DelProduct = 16,
92+
// Update authorities
93+
// key[0] upgrade authority [signer writable]
94+
// key[1] program account []
95+
// key[2] programdata account []
96+
// key[3] permissions account [writable]
97+
// key[4] system program []
98+
UpdPermissions = 17,
9299
}
93100

94101
#[repr(C)]
@@ -143,3 +150,12 @@ pub struct UpdPriceArgs {
143150
pub confidence: u64,
144151
pub publishing_slot: u64,
145152
}
153+
154+
#[repr(C)]
155+
#[derive(Zeroable, Pod, Copy, Clone)]
156+
pub struct UpdPermissionsArgs {
157+
pub header: CommandHeader,
158+
pub master_authority: Pubkey,
159+
pub data_curation_authority: Pubkey,
160+
pub security_authority: Pubkey,
161+
}

program/rust/src/processor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::rust_oracle::{
1919
init_price,
2020
resize_price_account,
2121
set_min_pub,
22+
upd_permissions,
2223
upd_price,
2324
upd_price_no_fail_on_error,
2425
upd_product,
@@ -52,5 +53,6 @@ pub fn process_instruction(
5253
}
5354
OracleCommand::DelPrice => del_price(program_id, accounts, instruction_data),
5455
OracleCommand::DelProduct => del_product(program_id, accounts, instruction_data),
56+
OracleCommand::UpdPermissions => upd_permissions(program_id, accounts, instruction_data),
5557
}
5658
}

program/rust/src/rust_oracle.rs

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::mem::{
55

66
use crate::c_oracle_header::{
77
MappingAccount,
8+
PermissionAccount,
89
PriceAccount,
910
PriceComponent,
1011
PriceEma,
@@ -18,8 +19,11 @@ use crate::c_oracle_header::{
1819
PC_PTYPE_UNKNOWN,
1920
PC_STATUS_UNKNOWN,
2021
PC_VERSION,
22+
PERMISSIONS_SEED,
2123
};
24+
2225
use crate::deserialize::{
26+
create_pda_if_needed,
2327
initialize_pyth_account_checked,
2428
load,
2529
load_checked,
@@ -31,33 +35,34 @@ use crate::instruction::{
3135
DelPublisherArgs,
3236
InitPriceArgs,
3337
SetMinPubArgs,
38+
UpdPermissionsArgs,
3439
UpdPriceArgs,
3540
};
3641
use crate::time_machine_types::PriceAccountWrapper;
3742
use crate::utils::{
3843
check_exponent_range,
44+
check_is_upgrade_authority_for_program,
3945
check_valid_funding_account,
4046
check_valid_signable_account,
4147
check_valid_writable_account,
4248
is_component_update,
4349
pyth_assert,
4450
read_pc_str_t,
51+
send_lamports,
4552
try_convert,
4653
};
4754
use crate::OracleError;
4855
use bytemuck::bytes_of_mut;
4956
use solana_program::account_info::AccountInfo;
5057
use solana_program::clock::Clock;
5158
use solana_program::entrypoint::ProgramResult;
52-
use solana_program::program::invoke;
5359
use solana_program::program_error::ProgramError;
5460
use solana_program::program_memory::{
5561
sol_memcpy,
5662
sol_memset,
5763
};
5864
use solana_program::pubkey::Pubkey;
5965
use solana_program::rent::Rent;
60-
use solana_program::system_instruction::transfer;
6166
use solana_program::system_program::check_id;
6267
use solana_program::sysvar::Sysvar;
6368

@@ -74,20 +79,6 @@ extern "C" {
7479
pub fn c_upd_aggregate(_input: *mut u8, clock_slot: u64, clock_timestamp: i64) -> bool;
7580
}
7681

77-
fn send_lamports<'a>(
78-
from: &AccountInfo<'a>,
79-
to: &AccountInfo<'a>,
80-
system_program: &AccountInfo<'a>,
81-
amount: u64,
82-
) -> Result<(), ProgramError> {
83-
let transfer_instruction = transfer(from.key, to.key, amount);
84-
invoke(
85-
&transfer_instruction,
86-
&[from.clone(), to.clone(), system_program.clone()],
87-
)?;
88-
Ok(())
89-
}
90-
9182
/// resizes a price account so that it fits the Time Machine
9283
/// key[0] funding account [signer writable]
9384
/// key[1] price account [Signer writable]
@@ -746,3 +737,63 @@ pub fn del_product(
746737

747738
Ok(())
748739
}
740+
741+
742+
/// Updates permissions for the pyth oracle program
743+
/// This function can create and update the permissions accounts, which stores
744+
/// several public keys that can execute administrative instructions in the pyth program
745+
pub fn upd_permissions(
746+
program_id: &Pubkey,
747+
accounts: &[AccountInfo],
748+
instruction_data: &[u8],
749+
) -> ProgramResult {
750+
let [funding_account, program_account, programdata_account, permissions_account, system_program] =
751+
match accounts {
752+
[v, w, x, y, z] => Ok([v, w, x, y, z]),
753+
_ => Err(ProgramError::InvalidArgument),
754+
}?;
755+
756+
let cmd_args = load::<UpdPermissionsArgs>(instruction_data)?;
757+
758+
check_valid_funding_account(funding_account)?;
759+
check_is_upgrade_authority_for_program(
760+
funding_account,
761+
program_account,
762+
programdata_account,
763+
program_id,
764+
)?;
765+
766+
let (permission_pda_address, bump_seed) =
767+
Pubkey::find_program_address(&[PERMISSIONS_SEED.as_bytes()], program_id);
768+
pyth_assert(
769+
permission_pda_address == *permissions_account.key,
770+
OracleError::InvalidPda.into(),
771+
)?;
772+
773+
pyth_assert(
774+
check_id(system_program.key),
775+
OracleError::InvalidSystemAccount.into(),
776+
)?;
777+
778+
779+
// Create permissions account if it doesn't exist
780+
create_pda_if_needed::<PermissionAccount>(
781+
permissions_account,
782+
funding_account,
783+
system_program,
784+
program_id,
785+
&[PERMISSIONS_SEED.as_bytes(), &[bump_seed]],
786+
cmd_args.header.version,
787+
)?;
788+
789+
check_valid_writable_account(program_id, permissions_account)?;
790+
791+
let mut permissions_account_data =
792+
load_checked::<PermissionAccount>(permissions_account, cmd_args.header.version)?;
793+
permissions_account_data.master_authority = cmd_args.master_authority;
794+
795+
permissions_account_data.data_curation_authority = cmd_args.data_curation_authority;
796+
permissions_account_data.security_authority = cmd_args.security_authority;
797+
798+
Ok(())
799+
}

program/rust/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod test_resize_account;
1212
mod test_set_min_pub;
1313
mod test_sizes;
1414
mod test_upd_aggregate;
15+
mod test_upd_permissions;
1516
mod test_upd_price;
1617
mod test_upd_price_no_fail_on_error;
1718
mod test_upd_product;

0 commit comments

Comments
 (0)