feat: separate write-authority from payer (#1373)

* feat: separate write-authority from payer

* test: update tests

* fix: typo
This commit is contained in:
guibescos 2024-03-20 19:04:34 +00:00 committed by GitHub
parent 6adfdb1beb
commit f79f205895
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 130 additions and 15 deletions

View File

@ -264,6 +264,7 @@ pub fn process_post_price_update_atomic(
let post_update_accounts = pyth_solana_receiver::accounts::PostUpdateAtomic::populate(
payer.pubkey(),
payer.pubkey(),
price_update_keypair.pubkey(),
*wormhole,
@ -472,6 +473,7 @@ pub fn process_write_encoded_vaa_and_post_price_update(
let price_update_keypair = Keypair::new();
let post_update_accounts = pyth_solana_receiver::accounts::PostUpdate::populate(
payer.pubkey(),
payer.pubkey(),
encoded_vaa_keypair.pubkey(),
price_update_keypair.pubkey(),

View File

@ -191,6 +191,7 @@ pub mod pyth_solana_receiver {
// End borrowed section
let payer = &ctx.accounts.payer;
let write_authority: &Signer<'_> = &ctx.accounts.write_authority;
let treasury = &ctx.accounts.treasury;
let price_update_account = &mut ctx.accounts.price_update_account;
@ -203,6 +204,7 @@ pub mod pyth_solana_receiver {
post_price_update_from_vaa(
config,
payer,
write_authority,
treasury,
price_update_account,
&vaa_components,
@ -220,6 +222,7 @@ pub mod pyth_solana_receiver {
pub fn post_update(ctx: Context<PostUpdate>, params: PostUpdateParams) -> Result<()> {
let config = &ctx.accounts.config;
let payer: &Signer<'_> = &ctx.accounts.payer;
let write_authority: &Signer<'_> = &ctx.accounts.write_authority;
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.
let treasury: &AccountInfo<'_> = &ctx.accounts.treasury;
let price_update_account: &mut Account<'_, PriceUpdateV1> =
@ -234,6 +237,7 @@ pub mod pyth_solana_receiver {
post_price_update_from_vaa(
config,
payer,
write_authority,
treasury,
price_update_account,
&vaa_components,
@ -297,11 +301,12 @@ pub struct PostUpdate<'info> {
/// CHECK: This is just a PDA controlled by the program. There is currently no way to withdraw funds from it.
#[account(mut, seeds = [TREASURY_SEED.as_ref(), &[params.treasury_id]], bump)]
pub treasury: AccountInfo<'info>,
/// The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.
/// The constraint is such that either the price_update_account is uninitialized or the write_authority is the write_authority.
/// 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
#[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)]
#[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)]
pub price_update_account: Account<'info, PriceUpdateV1>,
pub system_program: Program<'info, System>,
pub write_authority: Signer<'info>,
}
#[derive(Accounts)]
@ -319,11 +324,12 @@ pub struct PostUpdateAtomic<'info> {
#[account(mut, seeds = [TREASURY_SEED.as_ref(), &[params.treasury_id]], bump)]
/// CHECK: This is just a PDA controlled by the program. There is currently no way to withdraw funds from it.
pub treasury: AccountInfo<'info>,
/// The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.
/// The constraint is such that either the price_update_account is uninitialized or the write_authority is the write_authority.
/// 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
#[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)]
#[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)]
pub price_update_account: Account<'info, PriceUpdateV1>,
pub system_program: Program<'info, System>,
pub write_authority: Signer<'info>,
}
#[derive(Accounts)]
@ -388,6 +394,7 @@ struct VaaComponents {
fn post_price_update_from_vaa<'info>(
config: &Account<'info, Config>,
payer: &Signer<'info>,
write_authority: &Signer<'info>,
treasury: &AccountInfo<'info>,
price_update_account: &mut Account<'_, PriceUpdateV1>,
vaa_components: &VaaComponents,
@ -440,7 +447,7 @@ fn post_price_update_from_vaa<'info>(
match message {
Message::PriceFeedMessage(price_feed_message) => {
price_update_account.write_authority = payer.key();
price_update_account.write_authority = write_authority.key();
price_update_account.verification_level = vaa_components.verification_level;
price_update_account.price_message = price_feed_message;
}

View File

@ -43,6 +43,7 @@ impl accounts::Initialize {
impl accounts::PostUpdateAtomic {
pub fn populate(
payer: Pubkey,
write_authority: Pubkey,
price_update_account: Pubkey,
wormhole_address: Pubkey,
guardian_set_index: u32,
@ -60,12 +61,18 @@ impl accounts::PostUpdateAtomic {
treasury,
price_update_account,
system_program: system_program::ID,
write_authority,
}
}
}
impl accounts::PostUpdate {
pub fn populate(payer: Pubkey, encoded_vaa: Pubkey, price_update_account: Pubkey) -> Self {
pub fn populate(
payer: Pubkey,
write_authority: Pubkey,
encoded_vaa: Pubkey,
price_update_account: Pubkey,
) -> Self {
let config = get_config_address();
let treasury = get_treasury_address(DEFAULT_TREASURY_ID);
accounts::PostUpdate {
@ -75,6 +82,7 @@ impl accounts::PostUpdate {
treasury,
price_update_account,
system_program: system_program::ID,
write_authority,
}
}
}
@ -116,13 +124,18 @@ impl instruction::Initialize {
impl instruction::PostUpdate {
pub fn populate(
payer: Pubkey,
write_authority: Pubkey,
encoded_vaa: Pubkey,
price_update_account: Pubkey,
merkle_price_update: MerklePriceUpdate,
) -> Instruction {
let post_update_accounts =
accounts::PostUpdate::populate(payer, encoded_vaa, price_update_account)
.to_account_metas(None);
let post_update_accounts = accounts::PostUpdate::populate(
payer,
write_authority,
encoded_vaa,
price_update_account,
)
.to_account_metas(None);
Instruction {
program_id: ID,
accounts: post_update_accounts,
@ -141,6 +154,7 @@ impl instruction::PostUpdate {
impl instruction::PostUpdateAtomic {
pub fn populate(
payer: Pubkey,
write_authority: Pubkey,
price_update_account: Pubkey,
wormhole_address: Pubkey,
guardian_set_index: u32,
@ -150,6 +164,7 @@ impl instruction::PostUpdateAtomic {
) -> Instruction {
let post_update_accounts = accounts::PostUpdateAtomic::populate(
payer,
write_authority,
price_update_account,
wormhole_address,
guardian_set_index,

View File

@ -81,6 +81,7 @@ async fn test_invalid_wormhole_message() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -126,6 +127,7 @@ async fn test_invalid_update_message() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -177,6 +179,9 @@ async fn test_post_price_update_from_vaa() {
assert_treasury_balance(&mut program_simulator, 0, DEFAULT_TREASURY_ID).await;
let poster = program_simulator.get_funded_keypair().await.unwrap();
// poster_2 can't write to this price update account
let poster_2 = program_simulator.get_funded_keypair().await.unwrap();
let price_update_keypair = Keypair::new();
// this update is not in the proof
@ -184,6 +189,7 @@ async fn test_post_price_update_from_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -206,6 +212,7 @@ async fn test_post_price_update_from_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -245,6 +252,7 @@ async fn test_post_price_update_from_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -283,6 +291,7 @@ async fn test_post_price_update_from_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -320,6 +329,7 @@ async fn test_post_price_update_from_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -356,6 +366,47 @@ async fn test_post_price_update_from_vaa() {
feed_1
);
// Now poster_2 will pay
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster_2.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
DEFAULT_GUARDIAN_SET_INDEX,
vaa.clone(),
merkle_price_updates[0].clone(),
DEFAULT_TREASURY_ID,
),
&vec![&poster, &poster_2, &price_update_keypair],
None,
)
.await
.unwrap();
assert_treasury_balance(
&mut program_simulator,
Rent::default().minimum_balance(0) + 1,
DEFAULT_TREASURY_ID,
)
.await;
price_update_account = program_simulator
.get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
.await
.unwrap();
assert_eq!(price_update_account.write_authority, poster.pubkey());
assert_eq!(
price_update_account.verification_level,
VerificationLevel::Full
);
assert_eq!(
Message::PriceFeedMessage(price_update_account.price_message),
feed_1
);
// Now change the fee!
program_simulator
@ -378,6 +429,7 @@ async fn test_post_price_update_from_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -397,7 +449,7 @@ async fn test_post_price_update_from_vaa() {
assert_treasury_balance(
&mut program_simulator,
Rent::default().minimum_balance(0),
Rent::default().minimum_balance(0) + 1,
DEFAULT_TREASURY_ID,
)
.await;
@ -435,6 +487,7 @@ async fn test_post_price_update_from_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -451,7 +504,7 @@ async fn test_post_price_update_from_vaa() {
assert_treasury_balance(
&mut program_simulator,
Rent::default().minimum_balance(0) + LAMPORTS_PER_SOL,
Rent::default().minimum_balance(0) + 1 + LAMPORTS_PER_SOL,
DEFAULT_TREASURY_ID,
)
.await;
@ -470,14 +523,11 @@ async fn test_post_price_update_from_vaa() {
feed_2
);
// poster_2 can't write to this price update account
let poster_2 = program_simulator.get_funded_keypair().await.unwrap();
assert_eq!(
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster_2.pubkey(),
poster_2.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -494,4 +544,27 @@ async fn test_post_price_update_from_vaa() {
.unwrap(),
into_transaction_error(ReceiverError::WrongWriteAuthority)
);
// poster_2 can't write to this price update account not even if poster pays
assert_eq!(
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster_2.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
DEFAULT_GUARDIAN_SET_INDEX,
vaa.clone(),
merkle_price_updates[0].clone(),
DEFAULT_TREASURY_ID
),
&vec![&poster, &poster_2, &price_update_keypair],
None,
)
.await
.unwrap_err()
.unwrap(),
into_transaction_error(ReceiverError::WrongWriteAuthority)
);
}

View File

@ -68,6 +68,7 @@ async fn test_post_update() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdate::populate(
poster.pubkey(),
poster.pubkey(),
encoded_vaa_addresses[0],
price_update_keypair.pubkey(),
@ -105,6 +106,7 @@ async fn test_post_update() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdate::populate(
poster.pubkey(),
poster.pubkey(),
encoded_vaa_addresses[0],
price_update_keypair.pubkey(),
@ -195,6 +197,7 @@ async fn test_post_update_wrong_encoded_vaa_owner() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdate::populate(
poster.pubkey(),
poster.pubkey(),
Pubkey::new_unique(), // Random pubkey instead of the encoded VAA address
price_update_keypair.pubkey(),
@ -234,6 +237,7 @@ async fn test_post_update_wrong_setup() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdate::populate(
poster.pubkey(),
poster.pubkey(),
encoded_vaa_addresses[0],
price_update_keypair.pubkey(),

View File

@ -71,6 +71,7 @@ async fn test_post_update_atomic() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -111,6 +112,7 @@ async fn test_post_update_atomic() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -152,6 +154,7 @@ async fn test_post_update_atomic() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -217,6 +220,7 @@ async fn test_post_update_atomic_wrong_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -243,6 +247,7 @@ async fn test_post_update_atomic_wrong_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -268,6 +273,7 @@ async fn test_post_update_atomic_wrong_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -292,6 +298,7 @@ async fn test_post_update_atomic_wrong_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -316,6 +323,7 @@ async fn test_post_update_atomic_wrong_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -341,6 +349,7 @@ async fn test_post_update_atomic_wrong_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -365,6 +374,7 @@ async fn test_post_update_atomic_wrong_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -390,6 +400,7 @@ async fn test_post_update_atomic_wrong_vaa() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -408,6 +419,7 @@ async fn test_post_update_atomic_wrong_vaa() {
);
let mut wrong_instruction = PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -452,6 +464,7 @@ async fn test_post_update_atomic_wrong_setup() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,
@ -480,6 +493,7 @@ async fn test_post_update_atomic_wrong_setup() {
program_simulator
.process_ix_with_default_compute_limit(
PostUpdateAtomic::populate(
poster.pubkey(),
poster.pubkey(),
price_update_keypair.pubkey(),
BRIDGE_ID,