Skip to content

Commit 38ada8a

Browse files
committed
feat: init feed index when creating prices and init permission account
1 parent 9512981 commit 38ada8a

File tree

11 files changed

+141
-68
lines changed

11 files changed

+141
-68
lines changed

program/rust/src/accounts.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ pub trait PythAccount: Pod {
105105
/// have. `INITIAL_SIZE` <= `minimum_size()`
106106
const MINIMUM_SIZE: usize = size_of::<Self>();
107107

108+
/// Size of the account data on creation. Usually this is the same as `MINIMUM_SIZE` but it's
109+
/// different for `PermissionAccount` because we've added new fields to it. In this case
110+
/// we cannot increase `MINIMUM_SIZE` because that would break reading the account.
111+
const NEW_ACCOUNT_SPACE: usize = Self::MINIMUM_SIZE;
112+
108113
/// Given an `AccountInfo`, verify it is sufficiently large and has the correct discriminator.
109114
fn initialize<'a>(
110115
account: &'a AccountInfo,
@@ -139,15 +144,15 @@ pub trait PythAccount: Pod {
139144
seeds: &[&[u8]],
140145
version: u32,
141146
) -> Result<(), ProgramError> {
142-
let target_rent = get_rent()?.minimum_balance(Self::MINIMUM_SIZE);
147+
let target_rent = get_rent()?.minimum_balance(Self::NEW_ACCOUNT_SPACE);
143148

144149
if account.data_len() == 0 {
145150
create(
146151
funding_account,
147152
account,
148153
system_program,
149154
program_id,
150-
Self::MINIMUM_SIZE,
155+
Self::NEW_ACCOUNT_SPACE,
151156
target_rent,
152157
seeds,
153158
)?;

program/rust/src/accounts/permission.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ impl PermissionAccount {
7878

7979
impl PythAccount for PermissionAccount {
8080
const ACCOUNT_TYPE: u32 = PC_ACCTYPE_PERMISSIONS;
81-
// TODO: change?
82-
// TODO: add feed_index when creating account
83-
const INITIAL_SIZE: u32 = size_of::<PermissionAccount>() as u32;
81+
const INITIAL_SIZE: u32 = Self::MIN_SIZE_WITH_LAST_FEED_INDEX as u32;
82+
const NEW_ACCOUNT_SPACE: usize = Self::MIN_SIZE_WITH_LAST_FEED_INDEX;
8483
}

program/rust/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ pub enum OracleError {
5454
NeedsSuccesfulAggregation = 620,
5555
#[error("MaxLastFeedIndexReached")]
5656
MaxLastFeedIndexReached = 621,
57+
#[error("FeedIndexAlreadyInitialized")]
58+
FeedIndexAlreadyInitialized = 622,
5759
}
5860

5961
impl From<OracleError> for ProgramError {

program/rust/src/processor.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
use {
22
crate::{
3+
accounts::{
4+
AccountHeader,
5+
PermissionAccount,
6+
},
7+
deserialize::load_account_as_mut,
38
error::OracleError,
49
instruction::{
510
load_command_header_checked,
611
OracleCommand,
712
},
13+
utils::{
14+
pyth_assert,
15+
try_convert,
16+
},
817
},
918
solana_program::{
1019
entrypoint::ProgramResult,
20+
program::invoke,
1121
pubkey::Pubkey,
22+
system_instruction,
1223
sysvar::slot_history::AccountInfo,
1324
},
1425
};
@@ -33,7 +44,6 @@ pub use add_publisher::{
3344
DISABLE_ACCUMULATOR_V2,
3445
ENABLE_ACCUMULATOR_V2,
3546
};
36-
use init_price_feed_index::init_price_feed_index;
3747
pub use {
3848
add_price::add_price,
3949
add_product::add_product,
@@ -55,6 +65,14 @@ pub use {
5565
},
5666
upd_product::upd_product,
5767
};
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+
};
5876

5977
/// Dispatch to the right instruction in the oracle.
6078
pub fn process_instruction(
@@ -90,3 +108,41 @@ pub fn process_instruction(
90108
InitPriceFeedIndex => init_price_feed_index(program_id, accounts, instruction_data),
91109
}
92110
}
111+
112+
fn reserve_new_price_feed_index<'a>(
113+
funding_account: &AccountInfo<'a>,
114+
permissions_account: &AccountInfo<'a>,
115+
system_program: &AccountInfo<'a>,
116+
) -> Result<u32, ProgramError> {
117+
if permissions_account.data_len() < PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX {
118+
let new_size = PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX;
119+
let rent = Rent::get()?;
120+
let new_minimum_balance = rent.minimum_balance(new_size);
121+
let lamports_diff = new_minimum_balance.saturating_sub(permissions_account.lamports());
122+
if lamports_diff > 0 {
123+
invoke(
124+
&system_instruction::transfer(
125+
funding_account.key,
126+
permissions_account.key,
127+
lamports_diff,
128+
),
129+
&[
130+
funding_account.clone(),
131+
permissions_account.clone(),
132+
system_program.clone(),
133+
],
134+
)?;
135+
}
136+
137+
permissions_account.realloc(new_size, true)?;
138+
let mut header = load_account_as_mut::<AccountHeader>(permissions_account)?;
139+
header.size = try_convert(new_size)?;
140+
}
141+
let mut last_feed_index = PermissionAccount::load_last_feed_index_mut(permissions_account)?;
142+
*last_feed_index += 1;
143+
pyth_assert(
144+
*last_feed_index < (1 << 28),
145+
OracleError::MaxLastFeedIndexReached.into(),
146+
)?;
147+
Ok(*last_feed_index)
148+
}

program/rust/src/processor/add_price.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use {
2+
super::reserve_new_price_feed_index,
23
crate::{
34
accounts::{
45
PriceAccount,
@@ -18,6 +19,7 @@ use {
1819
check_exponent_range,
1920
check_permissioned_funding_account,
2021
check_valid_funding_account,
22+
check_valid_writable_account,
2123
pyth_assert,
2224
},
2325
OracleError,
@@ -31,9 +33,11 @@ use {
3133
};
3234

3335
/// Add new price account to a product account
34-
// account[0] funding account [signer writable]
35-
// account[1] product account [signer writable]
36-
// account[2] new price account [signer writable]
36+
// account[0] funding account [signer writable]
37+
// account[1] product account [writable]
38+
// account[2] new price account [writable]
39+
// account[3] permissions account [writable]
40+
// account[4] system program account []
3741
pub fn add_price(
3842
program_id: &Pubkey,
3943
accounts: &[AccountInfo],
@@ -48,10 +52,11 @@ pub fn add_price(
4852
)?;
4953

5054

51-
let (funding_account, product_account, price_account, permissions_account) = match accounts {
52-
[x, y, z, p] => Ok((x, y, z, p)),
53-
_ => Err(OracleError::InvalidNumberOfAccounts),
54-
}?;
55+
let (funding_account, product_account, price_account, permissions_account, system_program) =
56+
match accounts {
57+
[x, y, z, p, q] => Ok((x, y, z, p, q)),
58+
_ => Err(OracleError::InvalidNumberOfAccounts),
59+
}?;
5560

5661
check_valid_funding_account(funding_account)?;
5762
check_permissioned_funding_account(
@@ -68,16 +73,24 @@ pub fn add_price(
6873
permissions_account,
6974
&cmd_args.header,
7075
)?;
76+
check_valid_writable_account(program_id, permissions_account)?;
77+
pyth_assert(
78+
solana_program::system_program::check_id(system_program.key),
79+
OracleError::InvalidSystemAccount.into(),
80+
)?;
7181

7282
let mut product_data =
7383
load_checked::<ProductAccount>(product_account, cmd_args.header.version)?;
7484

85+
// TODO: where is it created???
7586
let mut price_data = PriceAccount::initialize(price_account, cmd_args.header.version)?;
7687
price_data.exponent = cmd_args.exponent;
7788
price_data.price_type = cmd_args.price_type;
7889
price_data.product_account = *product_account.key;
7990
price_data.next_price_account = product_data.first_price_account;
8091
price_data.min_pub_ = PRICE_ACCOUNT_DEFAULT_MIN_PUB;
92+
price_data.feed_index =
93+
reserve_new_price_feed_index(funding_account, permissions_account, system_program)?;
8194
product_data.first_price_account = *price_account.key;
8295

8396
Ok(())
Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
use {
2+
super::reserve_new_price_feed_index,
23
crate::{
3-
accounts::{
4-
PermissionAccount,
5-
PriceAccount,
6-
},
4+
accounts::PriceAccount,
75
deserialize::{
86
load,
97
load_checked,
@@ -12,28 +10,25 @@ use {
1210
utils::{
1311
check_permissioned_funding_account,
1412
check_valid_funding_account,
13+
check_valid_writable_account,
1514
pyth_assert,
1615
},
1716
OracleError,
1817
},
1918
solana_program::{
2019
account_info::AccountInfo,
2120
entrypoint::ProgramResult,
22-
program::invoke,
2321
program_error::ProgramError,
2422
pubkey::Pubkey,
25-
rent::Rent,
26-
system_instruction,
27-
sysvar::Sysvar,
2823
},
2924
std::mem::size_of,
3025
};
3126

3227
/// Init price feed index
33-
// account[0] funding account [signer writable]
34-
// account[1] price account [writable]
35-
// account[2] permissions account [writable]
36-
// account[3] system program account
28+
// account[0] funding account [signer writable]
29+
// account[1] price account [writable]
30+
// account[2] permissions account [writable]
31+
// account[3] system program account []
3732
pub fn init_price_feed_index(
3833
program_id: &Pubkey,
3934
accounts: &[AccountInfo],
@@ -59,42 +54,19 @@ pub fn init_price_feed_index(
5954
permissions_account,
6055
cmd,
6156
)?;
57+
check_valid_writable_account(program_id, permissions_account)?;
6258
pyth_assert(
6359
solana_program::system_program::check_id(system_program.key),
6460
OracleError::InvalidSystemAccount.into(),
6561
)?;
6662

67-
if permissions_account.data_len() < PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX {
68-
let new_size = PermissionAccount::MIN_SIZE_WITH_LAST_FEED_INDEX;
69-
let rent = Rent::get()?;
70-
let new_minimum_balance = rent.minimum_balance(new_size);
71-
let lamports_diff = new_minimum_balance.saturating_sub(permissions_account.lamports());
72-
if lamports_diff > 0 {
73-
invoke(
74-
&system_instruction::transfer(
75-
funding_account.key,
76-
permissions_account.key,
77-
lamports_diff,
78-
),
79-
&[
80-
funding_account.clone(),
81-
permissions_account.clone(),
82-
system_program.clone(),
83-
],
84-
)?;
85-
}
86-
87-
permissions_account.realloc(new_size, true)?;
88-
}
89-
let mut last_feed_index = PermissionAccount::load_last_feed_index_mut(permissions_account)?;
90-
*last_feed_index += 1;
63+
let mut price_account_data = load_checked::<PriceAccount>(price_account, cmd.version)?;
9164
pyth_assert(
92-
*last_feed_index < (1 << 28),
93-
OracleError::MaxLastFeedIndexReached.into(),
65+
price_account_data.feed_index == 0,
66+
OracleError::FeedIndexAlreadyInitialized.into(),
9467
)?;
95-
96-
let mut price_account_data = load_checked::<PriceAccount>(price_account, cmd.version)?;
97-
price_account_data.feed_index = *last_feed_index;
68+
price_account_data.feed_index =
69+
reserve_new_price_feed_index(funding_account, permissions_account, system_program)?;
9870

9971
Ok(())
10072
}

program/rust/src/tests/pyth_simulator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ impl PythSimulator {
338338
AccountMeta::new(product_keypair.pubkey(), true),
339339
AccountMeta::new(price_keypair.pubkey(), true),
340340
AccountMeta::new(self.get_permissions_pubkey(), false),
341+
AccountMeta::new(system_program::id(), false),
341342
],
342343
);
343344

program/rust/src/tests/test_add_price.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ fn test_add_price() {
6262
let mut permissions_setup = AccountSetup::new_permission(&program_id);
6363
let permissions_account = permissions_setup.as_account_info();
6464

65+
let mut system_program = AccountSetup::new_system_program();
66+
let system_program_account = system_program.as_account_info();
67+
6568
{
6669
let mut permissions_account_data =
6770
PermissionAccount::initialize(&permissions_account, PC_VERSION).unwrap();
@@ -82,17 +85,18 @@ fn test_add_price() {
8285
)
8386
.is_ok());
8487

85-
assert!(process_instruction(
88+
process_instruction(
8689
&program_id,
8790
&[
8891
funding_account.clone(),
8992
product_account.clone(),
9093
price_account.clone(),
9194
permissions_account.clone(),
95+
system_program_account.clone(),
9296
],
93-
instruction_data_add_price
97+
instruction_data_add_price,
9498
)
95-
.is_ok());
99+
.unwrap();
96100

97101
{
98102
let price_data = load_checked::<PriceAccount>(&price_account, PC_VERSION).unwrap();
@@ -105,17 +109,18 @@ fn test_add_price() {
105109
assert!(product_data.first_price_account == *price_account.key);
106110
}
107111

108-
assert!(process_instruction(
112+
process_instruction(
109113
&program_id,
110114
&[
111115
funding_account.clone(),
112116
product_account.clone(),
113117
price_account_2.clone(),
114118
permissions_account.clone(),
119+
system_program_account.clone(),
115120
],
116-
instruction_data_add_price
121+
instruction_data_add_price,
117122
)
118-
.is_ok());
123+
.unwrap();
119124

120125
{
121126
let price_data_2 = load_checked::<PriceAccount>(&price_account_2, PC_VERSION).unwrap();
@@ -137,6 +142,7 @@ fn test_add_price() {
137142
product_account.clone(),
138143
price_account.clone(),
139144
permissions_account.clone(),
145+
system_program_account.clone(),
140146
permissions_account.clone(),
141147
],
142148
instruction_data_add_price
@@ -153,6 +159,7 @@ fn test_add_price() {
153159
product_account.clone(),
154160
price_account.clone(),
155161
permissions_account.clone(),
162+
system_program_account.clone(),
156163
],
157164
instruction_data_add_price
158165
),
@@ -177,6 +184,7 @@ fn test_add_price() {
177184
product_account.clone(),
178185
price_account.clone(),
179186
permissions_account.clone(),
187+
system_program_account.clone(),
180188
],
181189
instruction_data_add_price
182190
),
@@ -202,6 +210,7 @@ fn test_add_price() {
202210
product_account.clone(),
203211
price_account.clone(),
204212
permissions_account.clone(),
213+
system_program_account.clone(),
205214
],
206215
instruction_data_add_price
207216
),

0 commit comments

Comments
 (0)