Skip to content

Commit f79f205

Browse files
authored
feat: separate write-authority from payer (#1373)
* feat: separate write-authority from payer * test: update tests * fix: typo
1 parent 6adfdb1 commit f79f205

File tree

6 files changed

+130
-15
lines changed

6 files changed

+130
-15
lines changed

target_chains/solana/cli/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ pub fn process_post_price_update_atomic(
264264

265265

266266
let post_update_accounts = pyth_solana_receiver::accounts::PostUpdateAtomic::populate(
267+
payer.pubkey(),
267268
payer.pubkey(),
268269
price_update_keypair.pubkey(),
269270
*wormhole,
@@ -472,6 +473,7 @@ pub fn process_write_encoded_vaa_and_post_price_update(
472473
let price_update_keypair = Keypair::new();
473474

474475
let post_update_accounts = pyth_solana_receiver::accounts::PostUpdate::populate(
476+
payer.pubkey(),
475477
payer.pubkey(),
476478
encoded_vaa_keypair.pubkey(),
477479
price_update_keypair.pubkey(),

target_chains/solana/programs/pyth-solana-receiver/src/lib.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ pub mod pyth_solana_receiver {
191191
// End borrowed section
192192

193193
let payer = &ctx.accounts.payer;
194+
let write_authority: &Signer<'_> = &ctx.accounts.write_authority;
194195
let treasury = &ctx.accounts.treasury;
195196
let price_update_account = &mut ctx.accounts.price_update_account;
196197

@@ -203,6 +204,7 @@ pub mod pyth_solana_receiver {
203204
post_price_update_from_vaa(
204205
config,
205206
payer,
207+
write_authority,
206208
treasury,
207209
price_update_account,
208210
&vaa_components,
@@ -220,6 +222,7 @@ pub mod pyth_solana_receiver {
220222
pub fn post_update(ctx: Context<PostUpdate>, params: PostUpdateParams) -> Result<()> {
221223
let config = &ctx.accounts.config;
222224
let payer: &Signer<'_> = &ctx.accounts.payer;
225+
let write_authority: &Signer<'_> = &ctx.accounts.write_authority;
223226
let encoded_vaa = VaaAccount::load(&ctx.accounts.encoded_vaa)?; // IMPORTANT: This line checks that the encoded_vaa has ProcessingStatus::Verified. This check is critical otherwise the program could be tricked into accepting unverified VAAs.
224227
let treasury: &AccountInfo<'_> = &ctx.accounts.treasury;
225228
let price_update_account: &mut Account<'_, PriceUpdateV1> =
@@ -234,6 +237,7 @@ pub mod pyth_solana_receiver {
234237
post_price_update_from_vaa(
235238
config,
236239
payer,
240+
write_authority,
237241
treasury,
238242
price_update_account,
239243
&vaa_components,
@@ -297,11 +301,12 @@ pub struct PostUpdate<'info> {
297301
/// CHECK: This is just a PDA controlled by the program. There is currently no way to withdraw funds from it.
298302
#[account(mut, seeds = [TREASURY_SEED.as_ref(), &[params.treasury_id]], bump)]
299303
pub treasury: AccountInfo<'info>,
300-
/// The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.
304+
/// The constraint is such that either the price_update_account is uninitialized or the write_authority is the write_authority.
301305
/// Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized
302-
#[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == payer.key() @ ReceiverError::WrongWriteAuthority , payer =payer, space = PriceUpdateV1::LEN)]
306+
#[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority , payer =payer, space = PriceUpdateV1::LEN)]
303307
pub price_update_account: Account<'info, PriceUpdateV1>,
304308
pub system_program: Program<'info, System>,
309+
pub write_authority: Signer<'info>,
305310
}
306311

307312
#[derive(Accounts)]
@@ -319,11 +324,12 @@ pub struct PostUpdateAtomic<'info> {
319324
#[account(mut, seeds = [TREASURY_SEED.as_ref(), &[params.treasury_id]], bump)]
320325
/// CHECK: This is just a PDA controlled by the program. There is currently no way to withdraw funds from it.
321326
pub treasury: AccountInfo<'info>,
322-
/// The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.
327+
/// The constraint is such that either the price_update_account is uninitialized or the write_authority is the write_authority.
323328
/// Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized
324-
#[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == payer.key() @ ReceiverError::WrongWriteAuthority, payer = payer, space = PriceUpdateV1::LEN)]
329+
#[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority, payer = payer, space = PriceUpdateV1::LEN)]
325330
pub price_update_account: Account<'info, PriceUpdateV1>,
326331
pub system_program: Program<'info, System>,
332+
pub write_authority: Signer<'info>,
327333
}
328334

329335
#[derive(Accounts)]
@@ -388,6 +394,7 @@ struct VaaComponents {
388394
fn post_price_update_from_vaa<'info>(
389395
config: &Account<'info, Config>,
390396
payer: &Signer<'info>,
397+
write_authority: &Signer<'info>,
391398
treasury: &AccountInfo<'info>,
392399
price_update_account: &mut Account<'_, PriceUpdateV1>,
393400
vaa_components: &VaaComponents,
@@ -440,7 +447,7 @@ fn post_price_update_from_vaa<'info>(
440447

441448
match message {
442449
Message::PriceFeedMessage(price_feed_message) => {
443-
price_update_account.write_authority = payer.key();
450+
price_update_account.write_authority = write_authority.key();
444451
price_update_account.verification_level = vaa_components.verification_level;
445452
price_update_account.price_message = price_feed_message;
446453
}

target_chains/solana/programs/pyth-solana-receiver/src/sdk.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ impl accounts::Initialize {
4343
impl accounts::PostUpdateAtomic {
4444
pub fn populate(
4545
payer: Pubkey,
46+
write_authority: Pubkey,
4647
price_update_account: Pubkey,
4748
wormhole_address: Pubkey,
4849
guardian_set_index: u32,
@@ -60,12 +61,18 @@ impl accounts::PostUpdateAtomic {
6061
treasury,
6162
price_update_account,
6263
system_program: system_program::ID,
64+
write_authority,
6365
}
6466
}
6567
}
6668

6769
impl accounts::PostUpdate {
68-
pub fn populate(payer: Pubkey, encoded_vaa: Pubkey, price_update_account: Pubkey) -> Self {
70+
pub fn populate(
71+
payer: Pubkey,
72+
write_authority: Pubkey,
73+
encoded_vaa: Pubkey,
74+
price_update_account: Pubkey,
75+
) -> Self {
6976
let config = get_config_address();
7077
let treasury = get_treasury_address(DEFAULT_TREASURY_ID);
7178
accounts::PostUpdate {
@@ -75,6 +82,7 @@ impl accounts::PostUpdate {
7582
treasury,
7683
price_update_account,
7784
system_program: system_program::ID,
85+
write_authority,
7886
}
7987
}
8088
}
@@ -116,13 +124,18 @@ impl instruction::Initialize {
116124
impl instruction::PostUpdate {
117125
pub fn populate(
118126
payer: Pubkey,
127+
write_authority: Pubkey,
119128
encoded_vaa: Pubkey,
120129
price_update_account: Pubkey,
121130
merkle_price_update: MerklePriceUpdate,
122131
) -> Instruction {
123-
let post_update_accounts =
124-
accounts::PostUpdate::populate(payer, encoded_vaa, price_update_account)
125-
.to_account_metas(None);
132+
let post_update_accounts = accounts::PostUpdate::populate(
133+
payer,
134+
write_authority,
135+
encoded_vaa,
136+
price_update_account,
137+
)
138+
.to_account_metas(None);
126139
Instruction {
127140
program_id: ID,
128141
accounts: post_update_accounts,
@@ -141,6 +154,7 @@ impl instruction::PostUpdate {
141154
impl instruction::PostUpdateAtomic {
142155
pub fn populate(
143156
payer: Pubkey,
157+
write_authority: Pubkey,
144158
price_update_account: Pubkey,
145159
wormhole_address: Pubkey,
146160
guardian_set_index: u32,
@@ -150,6 +164,7 @@ impl instruction::PostUpdateAtomic {
150164
) -> Instruction {
151165
let post_update_accounts = accounts::PostUpdateAtomic::populate(
152166
payer,
167+
write_authority,
153168
price_update_account,
154169
wormhole_address,
155170
guardian_set_index,

target_chains/solana/programs/pyth-solana-receiver/tests/test_post_price_update_from_vaa.rs

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ async fn test_invalid_wormhole_message() {
8181
program_simulator
8282
.process_ix_with_default_compute_limit(
8383
PostUpdateAtomic::populate(
84+
poster.pubkey(),
8485
poster.pubkey(),
8586
price_update_keypair.pubkey(),
8687
BRIDGE_ID,
@@ -126,6 +127,7 @@ async fn test_invalid_update_message() {
126127
program_simulator
127128
.process_ix_with_default_compute_limit(
128129
PostUpdateAtomic::populate(
130+
poster.pubkey(),
129131
poster.pubkey(),
130132
price_update_keypair.pubkey(),
131133
BRIDGE_ID,
@@ -177,13 +179,17 @@ async fn test_post_price_update_from_vaa() {
177179
assert_treasury_balance(&mut program_simulator, 0, DEFAULT_TREASURY_ID).await;
178180

179181
let poster = program_simulator.get_funded_keypair().await.unwrap();
182+
// poster_2 can't write to this price update account
183+
let poster_2 = program_simulator.get_funded_keypair().await.unwrap();
184+
180185
let price_update_keypair = Keypair::new();
181186

182187
// this update is not in the proof
183188
assert_eq!(
184189
program_simulator
185190
.process_ix_with_default_compute_limit(
186191
PostUpdateAtomic::populate(
192+
poster.pubkey(),
187193
poster.pubkey(),
188194
price_update_keypair.pubkey(),
189195
BRIDGE_ID,
@@ -206,6 +212,7 @@ async fn test_post_price_update_from_vaa() {
206212
program_simulator
207213
.process_ix_with_default_compute_limit(
208214
PostUpdateAtomic::populate(
215+
poster.pubkey(),
209216
poster.pubkey(),
210217
price_update_keypair.pubkey(),
211218
BRIDGE_ID,
@@ -245,6 +252,7 @@ async fn test_post_price_update_from_vaa() {
245252
program_simulator
246253
.process_ix_with_default_compute_limit(
247254
PostUpdateAtomic::populate(
255+
poster.pubkey(),
248256
poster.pubkey(),
249257
price_update_keypair.pubkey(),
250258
BRIDGE_ID,
@@ -283,6 +291,7 @@ async fn test_post_price_update_from_vaa() {
283291
program_simulator
284292
.process_ix_with_default_compute_limit(
285293
PostUpdateAtomic::populate(
294+
poster.pubkey(),
286295
poster.pubkey(),
287296
price_update_keypair.pubkey(),
288297
BRIDGE_ID,
@@ -320,6 +329,7 @@ async fn test_post_price_update_from_vaa() {
320329
program_simulator
321330
.process_ix_with_default_compute_limit(
322331
PostUpdateAtomic::populate(
332+
poster.pubkey(),
323333
poster.pubkey(),
324334
price_update_keypair.pubkey(),
325335
BRIDGE_ID,
@@ -356,6 +366,47 @@ async fn test_post_price_update_from_vaa() {
356366
feed_1
357367
);
358368

369+
// Now poster_2 will pay
370+
program_simulator
371+
.process_ix_with_default_compute_limit(
372+
PostUpdateAtomic::populate(
373+
poster_2.pubkey(),
374+
poster.pubkey(),
375+
price_update_keypair.pubkey(),
376+
BRIDGE_ID,
377+
DEFAULT_GUARDIAN_SET_INDEX,
378+
vaa.clone(),
379+
merkle_price_updates[0].clone(),
380+
DEFAULT_TREASURY_ID,
381+
),
382+
&vec![&poster, &poster_2, &price_update_keypair],
383+
None,
384+
)
385+
.await
386+
.unwrap();
387+
388+
assert_treasury_balance(
389+
&mut program_simulator,
390+
Rent::default().minimum_balance(0) + 1,
391+
DEFAULT_TREASURY_ID,
392+
)
393+
.await;
394+
395+
price_update_account = program_simulator
396+
.get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
397+
.await
398+
.unwrap();
399+
400+
assert_eq!(price_update_account.write_authority, poster.pubkey());
401+
assert_eq!(
402+
price_update_account.verification_level,
403+
VerificationLevel::Full
404+
);
405+
assert_eq!(
406+
Message::PriceFeedMessage(price_update_account.price_message),
407+
feed_1
408+
);
409+
359410

360411
// Now change the fee!
361412
program_simulator
@@ -378,6 +429,7 @@ async fn test_post_price_update_from_vaa() {
378429
program_simulator
379430
.process_ix_with_default_compute_limit(
380431
PostUpdateAtomic::populate(
432+
poster.pubkey(),
381433
poster.pubkey(),
382434
price_update_keypair.pubkey(),
383435
BRIDGE_ID,
@@ -397,7 +449,7 @@ async fn test_post_price_update_from_vaa() {
397449

398450
assert_treasury_balance(
399451
&mut program_simulator,
400-
Rent::default().minimum_balance(0),
452+
Rent::default().minimum_balance(0) + 1,
401453
DEFAULT_TREASURY_ID,
402454
)
403455
.await;
@@ -435,6 +487,7 @@ async fn test_post_price_update_from_vaa() {
435487
program_simulator
436488
.process_ix_with_default_compute_limit(
437489
PostUpdateAtomic::populate(
490+
poster.pubkey(),
438491
poster.pubkey(),
439492
price_update_keypair.pubkey(),
440493
BRIDGE_ID,
@@ -451,7 +504,7 @@ async fn test_post_price_update_from_vaa() {
451504

452505
assert_treasury_balance(
453506
&mut program_simulator,
454-
Rent::default().minimum_balance(0) + LAMPORTS_PER_SOL,
507+
Rent::default().minimum_balance(0) + 1 + LAMPORTS_PER_SOL,
455508
DEFAULT_TREASURY_ID,
456509
)
457510
.await;
@@ -470,14 +523,11 @@ async fn test_post_price_update_from_vaa() {
470523
feed_2
471524
);
472525

473-
474-
// poster_2 can't write to this price update account
475-
let poster_2 = program_simulator.get_funded_keypair().await.unwrap();
476-
477526
assert_eq!(
478527
program_simulator
479528
.process_ix_with_default_compute_limit(
480529
PostUpdateAtomic::populate(
530+
poster_2.pubkey(),
481531
poster_2.pubkey(),
482532
price_update_keypair.pubkey(),
483533
BRIDGE_ID,
@@ -494,4 +544,27 @@ async fn test_post_price_update_from_vaa() {
494544
.unwrap(),
495545
into_transaction_error(ReceiverError::WrongWriteAuthority)
496546
);
547+
548+
// poster_2 can't write to this price update account not even if poster pays
549+
assert_eq!(
550+
program_simulator
551+
.process_ix_with_default_compute_limit(
552+
PostUpdateAtomic::populate(
553+
poster.pubkey(),
554+
poster_2.pubkey(),
555+
price_update_keypair.pubkey(),
556+
BRIDGE_ID,
557+
DEFAULT_GUARDIAN_SET_INDEX,
558+
vaa.clone(),
559+
merkle_price_updates[0].clone(),
560+
DEFAULT_TREASURY_ID
561+
),
562+
&vec![&poster, &poster_2, &price_update_keypair],
563+
None,
564+
)
565+
.await
566+
.unwrap_err()
567+
.unwrap(),
568+
into_transaction_error(ReceiverError::WrongWriteAuthority)
569+
);
497570
}

target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ async fn test_post_update() {
6868
program_simulator
6969
.process_ix_with_default_compute_limit(
7070
PostUpdate::populate(
71+
poster.pubkey(),
7172
poster.pubkey(),
7273
encoded_vaa_addresses[0],
7374
price_update_keypair.pubkey(),
@@ -105,6 +106,7 @@ async fn test_post_update() {
105106
program_simulator
106107
.process_ix_with_default_compute_limit(
107108
PostUpdate::populate(
109+
poster.pubkey(),
108110
poster.pubkey(),
109111
encoded_vaa_addresses[0],
110112
price_update_keypair.pubkey(),
@@ -195,6 +197,7 @@ async fn test_post_update_wrong_encoded_vaa_owner() {
195197
program_simulator
196198
.process_ix_with_default_compute_limit(
197199
PostUpdate::populate(
200+
poster.pubkey(),
198201
poster.pubkey(),
199202
Pubkey::new_unique(), // Random pubkey instead of the encoded VAA address
200203
price_update_keypair.pubkey(),
@@ -234,6 +237,7 @@ async fn test_post_update_wrong_setup() {
234237
program_simulator
235238
.process_ix_with_default_compute_limit(
236239
PostUpdate::populate(
240+
poster.pubkey(),
237241
poster.pubkey(),
238242
encoded_vaa_addresses[0],
239243
price_update_keypair.pubkey(),

0 commit comments

Comments
 (0)