소스 검색

feat(solana-receiver-cli): Add `post-twap-update` cmd to CLI, update TWAP data model & validations (#2180)

* feat: removed verification_level & posted_slot from TwapUpdate struct, added post twap subcommand to CLI

* refactor: address pr comments
Tejas Badadare 11 달 전
부모
커밋
6b611c820e

+ 1 - 1
target_chains/solana/Cargo.lock

@@ -3085,7 +3085,7 @@ dependencies = [
 
 [[package]]
 name = "pyth-solana-receiver-cli"
-version = "0.1.0"
+version = "0.2.0"
 dependencies = [
  "anchor-client",
  "anyhow",

+ 7 - 7
target_chains/solana/cli/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "pyth-solana-receiver-cli"
-version = "0.1.0"
+version = "0.2.0"
 edition = "2021"
 
 [dependencies]
@@ -10,13 +10,13 @@ shellexpand = "2.1.2"
 solana-sdk = { workspace = true }
 solana-client = { workspace = true }
 anchor-client = { workspace = true }
-clap = {version ="3.2.22", features = ["derive"]}
-pyth-solana-receiver = {path = "../programs/pyth-solana-receiver" }
-wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"} # Used for initializing the wormhole receiver
+clap = { version = "3.2.22", features = ["derive"] }
+pyth-solana-receiver = { path = "../programs/pyth-solana-receiver" }
+wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana" } # Used for initializing the wormhole receiver
 pythnet-sdk = { path = "../../../pythnet/pythnet_sdk", version = "2.0.0" }
 wormhole-vaas-serde = { workspace = true }
 serde_wormhole = { workspace = true }
 hex = "0.4.3"
-borsh = "0.9.3" # Old version of borsh needed for wormhole-solana
-wormhole-core-bridge-solana = {workspace = true}
-pyth-solana-receiver-sdk = {path = "../pyth_solana_receiver_sdk"}
+borsh = "0.9.3"                                                                                   # Old version of borsh needed for wormhole-solana
+wormhole-core-bridge-solana = { workspace = true }
+pyth-solana-receiver-sdk = { path = "../pyth_solana_receiver_sdk" }

+ 16 - 1
target_chains/solana/cli/src/cli.rs

@@ -48,12 +48,27 @@ pub enum Action {
         )]
         n_signatures: usize,
     },
+    #[clap(about = "Post a TWAP update from Hermes to Solana")]
+    PostTwapUpdate {
+        #[clap(
+            short = 's',
+            long,
+            help = "Start base64 data from Hermes (binary.data.0)"
+        )]
+        start_payload: String,
+        #[clap(
+            short = 'e',
+            long,
+            help = "End base64 data from Hermes (binary.data.1)"
+        )]
+        end_payload: String,
+    },
     #[clap(
         about = "Initialize a wormhole receiver contract by sequentially replaying the guardian set updates"
     )]
     InitializeWormholeReceiver {},
     InitializePythReceiver {
-        #[clap(short = 'f', long, help = "Fee in lmaports")]
+        #[clap(short = 'f', long, help = "Fee in lamports")]
         fee: u64,
         #[clap(short = 'e', long, parse(try_from_str = Pubkey::from_str), help = "Source emitter")]
         emitter: Pubkey,

+ 197 - 48
target_chains/solana/cli/src/main.rs

@@ -92,7 +92,32 @@ fn main() -> Result<()> {
                 &merkle_price_updates[0],
             )?;
         }
+        Action::PostTwapUpdate {
+            start_payload,
+            end_payload,
+        } => {
+            let rpc_client = RpcClient::new(url);
+            let payer =
+                read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");
 
+            let start_payload_bytes: Vec<u8> = base64::decode(start_payload)?;
+            let end_payload_bytes: Vec<u8> = base64::decode(end_payload)?;
+
+            let (start_vaa, start_merkle_price_updates) =
+                deserialize_accumulator_update_data(start_payload_bytes)?;
+            let (end_vaa, end_merkle_price_updates) =
+                deserialize_accumulator_update_data(end_payload_bytes)?;
+
+            process_write_encoded_vaa_and_post_twap_update(
+                &rpc_client,
+                &start_vaa,
+                &end_vaa,
+                wormhole,
+                &payer,
+                &start_merkle_price_updates[0],
+                &end_merkle_price_updates[0],
+            )?;
+        }
         Action::InitializeWormholeReceiver {} => {
             let rpc_client = RpcClient::new(url);
             let payer =
@@ -367,36 +392,174 @@ pub fn process_write_encoded_vaa_and_post_price_update(
     merkle_price_update: &MerklePriceUpdate,
 ) -> Result<Pubkey> {
     let encoded_vaa_keypair = Keypair::new();
+
+    // Transaction 1: Create and initialize VAA
+    let init_instructions = init_encoded_vaa_and_write_initial_data_ixs(
+        &payer.pubkey(),
+        vaa,
+        &wormhole,
+        &encoded_vaa_keypair,
+    )?;
+    process_transaction(
+        rpc_client,
+        init_instructions,
+        &vec![payer, &encoded_vaa_keypair],
+    )?;
+
+    // Transaction 2: Write remaining VAA data, verify VAA, and post price update
+    let price_update_keypair = Keypair::new();
+    let mut update_instructions = vec![ComputeBudgetInstruction::set_compute_unit_limit(600_000)];
+
+    update_instructions.extend(write_remaining_data_and_verify_vaa_ixs(
+        &payer.pubkey(),
+        vaa,
+        &encoded_vaa_keypair.pubkey(),
+        wormhole,
+    )?);
+
+    update_instructions.push(pyth_solana_receiver::instruction::PostUpdate::populate(
+        payer.pubkey(),
+        payer.pubkey(),
+        encoded_vaa_keypair.pubkey(),
+        price_update_keypair.pubkey(),
+        merkle_price_update.clone(),
+        get_random_treasury_id(),
+    ));
+
+    process_transaction(
+        rpc_client,
+        update_instructions,
+        &vec![payer, &price_update_keypair],
+    )?;
+
+    Ok(price_update_keypair.pubkey())
+}
+
+/// This function verifies start & end VAAs from Hermes via Wormhole to produce encoded VAAs,
+/// and then posts a TWAP update using the encoded VAAs. Returns the TwapUpdate account pubkey.
+///
+/// The operation is split up into 4 transactions:
+/// 1. Creates and initializes the start VAA account and writes its first part
+/// 2. Creates and initializes the end VAA account and writes its first part
+/// 3. Writes the remaining data for both VAAs and verifies them
+/// 4. Posts the TWAP update
+pub fn process_write_encoded_vaa_and_post_twap_update(
+    rpc_client: &RpcClient,
+    start_vaa: &[u8],
+    end_vaa: &[u8],
+    wormhole: Pubkey,
+    payer: &Keypair,
+    start_merkle_price_update: &MerklePriceUpdate,
+    end_merkle_price_update: &MerklePriceUpdate,
+) -> Result<Pubkey> {
+    // Create keypairs for both encoded VAAs
+    let start_encoded_vaa_keypair = Keypair::new();
+    let end_encoded_vaa_keypair = Keypair::new();
+
+    // Transaction 1: Create and initialize start VAA
+    let start_init_instructions = init_encoded_vaa_and_write_initial_data_ixs(
+        &payer.pubkey(),
+        start_vaa,
+        &wormhole,
+        &start_encoded_vaa_keypair,
+    )?;
+    process_transaction(
+        rpc_client,
+        start_init_instructions,
+        &vec![payer, &start_encoded_vaa_keypair],
+    )?;
+
+    // Transaction 2: Create and initialize end VAA
+    let end_init_instructions = init_encoded_vaa_and_write_initial_data_ixs(
+        &payer.pubkey(),
+        end_vaa,
+        &wormhole,
+        &end_encoded_vaa_keypair,
+    )?;
+    process_transaction(
+        rpc_client,
+        end_init_instructions,
+        &vec![payer, &end_encoded_vaa_keypair],
+    )?;
+
+    // Transaction 3: Write remaining VAA data and verify both VAAs
+    let mut verify_instructions = vec![ComputeBudgetInstruction::set_compute_unit_limit(400_000)];
+    verify_instructions.extend(write_remaining_data_and_verify_vaa_ixs(
+        &payer.pubkey(),
+        start_vaa,
+        &start_encoded_vaa_keypair.pubkey(),
+        wormhole,
+    )?);
+    verify_instructions.extend(write_remaining_data_and_verify_vaa_ixs(
+        &payer.pubkey(),
+        end_vaa,
+        &end_encoded_vaa_keypair.pubkey(),
+        wormhole,
+    )?);
+    process_transaction(rpc_client, verify_instructions, &vec![payer])?;
+
+    // Transaction 4: Post TWAP update
+    let twap_update_keypair = Keypair::new();
+    let post_instructions = vec![
+        ComputeBudgetInstruction::set_compute_unit_limit(400_000),
+        pyth_solana_receiver::instruction::PostTwapUpdate::populate(
+            payer.pubkey(),
+            payer.pubkey(),
+            start_encoded_vaa_keypair.pubkey(),
+            end_encoded_vaa_keypair.pubkey(),
+            twap_update_keypair.pubkey(),
+            start_merkle_price_update.clone(),
+            end_merkle_price_update.clone(),
+            get_random_treasury_id(),
+        ),
+    ];
+    process_transaction(
+        rpc_client,
+        post_instructions,
+        &vec![payer, &twap_update_keypair],
+    )?;
+
+    Ok(twap_update_keypair.pubkey())
+}
+
+/// Creates instructions to initialize an encoded VAA account and write the first part of the VAA data
+pub fn init_encoded_vaa_and_write_initial_data_ixs(
+    payer: &Pubkey,
+    vaa: &[u8],
+    wormhole: &Pubkey,
+    encoded_vaa_keypair: &Keypair,
+) -> Result<Vec<Instruction>> {
     let encoded_vaa_size: usize = vaa.len() + VAA_START;
 
     let create_encoded_vaa = system_instruction::create_account(
-        &payer.pubkey(),
+        payer,
         &encoded_vaa_keypair.pubkey(),
         Rent::default().minimum_balance(encoded_vaa_size),
         encoded_vaa_size as u64,
-        &wormhole,
+        wormhole,
     );
+
     let init_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::InitEncodedVaa {
-        write_authority: payer.pubkey(),
+        write_authority: *payer,
         encoded_vaa: encoded_vaa_keypair.pubkey(),
     }
     .to_account_metas(None);
 
     let init_encoded_vaa_instruction = Instruction {
-        program_id: wormhole,
+        program_id: *wormhole,
         accounts: init_encoded_vaa_accounts,
         data: wormhole_core_bridge_solana::instruction::InitEncodedVaa.data(),
     };
 
     let write_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::WriteEncodedVaa {
-        write_authority: payer.pubkey(),
+        write_authority: *payer,
         draft_vaa: encoded_vaa_keypair.pubkey(),
     }
     .to_account_metas(None);
 
-    let write_encoded_vaa_accounts_instruction = Instruction {
-        program_id: wormhole,
-        accounts: write_encoded_vaa_accounts.clone(),
+    let write_encoded_vaa_instruction = Instruction {
+        program_id: *wormhole,
+        accounts: write_encoded_vaa_accounts,
         data: wormhole_core_bridge_solana::instruction::WriteEncodedVaa {
             args: WriteEncodedVaaArgs {
                 index: 0,
@@ -406,18 +569,27 @@ pub fn process_write_encoded_vaa_and_post_price_update(
         .data(),
     };
 
-    // 1st transaction
-    process_transaction(
-        rpc_client,
-        vec![
-            create_encoded_vaa,
-            init_encoded_vaa_instruction,
-            write_encoded_vaa_accounts_instruction,
-        ],
-        &vec![payer, &encoded_vaa_keypair],
-    )?;
+    Ok(vec![
+        create_encoded_vaa,
+        init_encoded_vaa_instruction,
+        write_encoded_vaa_instruction,
+    ])
+}
+
+/// Creates instructions to write remaining VAA data and verify the VAA
+pub fn write_remaining_data_and_verify_vaa_ixs(
+    payer: &Pubkey,
+    vaa: &[u8],
+    encoded_vaa_keypair: &Pubkey,
+    wormhole: Pubkey,
+) -> Result<Vec<Instruction>> {
+    let write_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::WriteEncodedVaa {
+        write_authority: *payer,
+        draft_vaa: *encoded_vaa_keypair,
+    }
+    .to_account_metas(None);
 
-    let write_encoded_vaa_accounts_instruction_2 = Instruction {
+    let write_encoded_vaa_instruction = Instruction {
         program_id: wormhole,
         accounts: write_encoded_vaa_accounts,
         data: wormhole_core_bridge_solana::instruction::WriteEncodedVaa {
@@ -432,13 +604,10 @@ pub fn process_write_encoded_vaa_and_post_price_update(
     let (header, _): (Header, Body<&RawMessage>) = serde_wormhole::from_slice(vaa).unwrap();
     let guardian_set = GuardianSet::key(&wormhole, header.guardian_set_index);
 
-    let request_compute_units_instruction: Instruction =
-        ComputeBudgetInstruction::set_compute_unit_limit(600_000);
-
     let verify_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::VerifyEncodedVaaV1 {
         guardian_set,
-        write_authority: payer.pubkey(),
-        draft_vaa: encoded_vaa_keypair.pubkey(),
+        write_authority: *payer,
+        draft_vaa: *encoded_vaa_keypair,
     }
     .to_account_metas(None);
 
@@ -448,30 +617,10 @@ pub fn process_write_encoded_vaa_and_post_price_update(
         data: wormhole_core_bridge_solana::instruction::VerifyEncodedVaaV1 {}.data(),
     };
 
-    let price_update_keypair = Keypair::new();
-
-    let post_update_instructions = pyth_solana_receiver::instruction::PostUpdate::populate(
-        payer.pubkey(),
-        payer.pubkey(),
-        encoded_vaa_keypair.pubkey(),
-        price_update_keypair.pubkey(),
-        merkle_price_update.clone(),
-        get_random_treasury_id(),
-    );
-
-    // 2nd transaction
-    process_transaction(
-        rpc_client,
-        vec![
-            request_compute_units_instruction,
-            write_encoded_vaa_accounts_instruction_2,
-            verify_encoded_vaa_instruction,
-            post_update_instructions,
-        ],
-        &vec![payer, &price_update_keypair],
-    )?;
-
-    Ok(price_update_keypair.pubkey())
+    Ok(vec![
+        write_encoded_vaa_instruction,
+        verify_encoded_vaa_instruction,
+    ])
 }
 
 pub fn process_transaction(

+ 4 - 0
target_chains/solana/programs/pyth-solana-receiver/src/error.rs

@@ -16,6 +16,10 @@ pub enum ReceiverError {
     #[msg("Funds are insufficient to pay the receiving fee")]
     InsufficientFunds,
     #[msg("Cannot calculate TWAP, end slot must be greater than start slot")]
+    FeedIdMismatch,
+    #[msg("The start and end messages must have the same feed ID")]
+    ExponentMismatch,
+    #[msg("The start and end messages must have the same exponent")]
     InvalidTwapSlots,
     #[msg("Start message is not the first update for its timestamp")]
     InvalidTwapStartMessage,

+ 51 - 10
target_chains/solana/programs/pyth-solana-receiver/src/lib.rs

@@ -233,6 +233,8 @@ pub mod pyth_solana_receiver {
     }
 
     /// Post a TWAP (time weighted average price) update for a given time window.
+    /// This should be called after the client has already verified the VAAs via the Wormhole contract.
+    /// Check out target_chains/solana/cli/src/main.rs for an example of how to do this.
     pub fn post_twap_update(
         ctx: Context<PostTwapUpdate>,
         params: PostTwapUpdateParams,
@@ -483,11 +485,12 @@ fn post_twap_update_from_vaas<'info>(
     // Calculate the TWAP and store it in the output account
     match (start_message, end_message) {
         (Message::TwapMessage(start_msg), Message::TwapMessage(end_msg)) => {
+            // Verify that the feed ids and expos match, the start msg was published before the end msg,
+            // and that they are the first messages within their slots
+            validate_twap_messages(&start_msg, &end_msg)?;
             let (price, conf, down_slots_ratio) = calculate_twap(&start_msg, &end_msg)?;
 
             twap_update_account.write_authority = write_authority.key();
-            twap_update_account.verification_level = start_vaa_components.verification_level;
-
             twap_update_account.twap.feed_id = start_msg.feed_id;
             twap_update_account.twap.start_time = start_msg.publish_time;
             twap_update_account.twap.end_time = end_msg.publish_time;
@@ -495,8 +498,6 @@ fn post_twap_update_from_vaas<'info>(
             twap_update_account.twap.conf = conf;
             twap_update_account.twap.exponent = start_msg.exponent;
             twap_update_account.twap.down_slots_ratio = down_slots_ratio;
-
-            twap_update_account.posted_slot = Clock::get()?.slot;
         }
         _ => {
             return err!(ReceiverError::UnsupportedMessageType);
@@ -506,7 +507,19 @@ fn post_twap_update_from_vaas<'info>(
     Ok(())
 }
 
-fn calculate_twap(start_msg: &TwapMessage, end_msg: &TwapMessage) -> Result<(i64, u64, u32)> {
+fn validate_twap_messages(start_msg: &TwapMessage, end_msg: &TwapMessage) -> Result<()> {
+    // Validate feed ids match
+    require!(
+        start_msg.feed_id == end_msg.feed_id,
+        ReceiverError::FeedIdMismatch
+    );
+
+    // Validate exponents match
+    require!(
+        start_msg.exponent == end_msg.exponent,
+        ReceiverError::ExponentMismatch
+    );
+
     // Validate slots
     require!(
         end_msg.publish_slot > start_msg.publish_slot,
@@ -522,6 +535,12 @@ fn calculate_twap(start_msg: &TwapMessage, end_msg: &TwapMessage) -> Result<(i64
         end_msg.prev_publish_time < end_msg.publish_time,
         ReceiverError::InvalidTwapEndMessage
     );
+    Ok(())
+}
+
+/// Calculate the TWAP for the window before start and end messages
+/// Warning: The parameters aren't checked for validity, call `validate_twap_messages` before using.
+fn calculate_twap(start_msg: &TwapMessage, end_msg: &TwapMessage) -> Result<(i64, u64, u32)> {
     let slot_diff = end_msg
         .publish_slot
         .checked_sub(start_msg.publish_slot)
@@ -659,8 +678,8 @@ fn verify_vaa_data_source(
 }
 
 #[cfg(test)]
-/// Unit tests for the core TWAP calculation logic in `calculate_twap`
-/// This test module is here because `calculate_twap` is private and can't
+/// Unit tests for the core TWAP calculation logic in `calculate_twap` and `validate_twap_messages`
+/// This test module is here because these functions are private and can't
 /// be imported into `tests/test_post_twap_updates`.
 mod calculate_twap_unit_tests {
     use super::*;
@@ -688,6 +707,7 @@ mod calculate_twap_unit_tests {
         let start = create_basic_twap_message(100, 100, 90, 1000);
         let end = create_basic_twap_message(300, 200, 180, 1100);
 
+        validate_twap_messages(&start, &end).unwrap();
         let price = calculate_twap(&start, &end).unwrap();
         assert_eq!(price.0, 2); // (300-100)/(1100-1000) = 2
     }
@@ -697,7 +717,7 @@ mod calculate_twap_unit_tests {
         let start = create_basic_twap_message(100, 100, 90, 1100);
         let end = create_basic_twap_message(300, 200, 180, 1000);
 
-        let err = calculate_twap(&start, &end).unwrap_err();
+        let err = validate_twap_messages(&start, &end).unwrap_err();
         assert_eq!(err, ReceiverError::InvalidTwapSlots.into());
     }
 
@@ -706,13 +726,13 @@ mod calculate_twap_unit_tests {
         let start = create_basic_twap_message(100, 100, 110, 1000);
         let end = create_basic_twap_message(300, 200, 180, 1100);
 
-        let err = calculate_twap(&start, &end).unwrap_err();
+        let err = validate_twap_messages(&start, &end).unwrap_err();
         assert_eq!(err, ReceiverError::InvalidTwapStartMessage.into());
 
         let start = create_basic_twap_message(100, 100, 90, 1000);
         let end = create_basic_twap_message(300, 200, 200, 1100);
 
-        let err = calculate_twap(&start, &end).unwrap_err();
+        let err = validate_twap_messages(&start, &end).unwrap_err();
         assert_eq!(err, ReceiverError::InvalidTwapEndMessage.into());
     }
 
@@ -721,7 +741,28 @@ mod calculate_twap_unit_tests {
         let start = create_basic_twap_message(i128::MIN, 100, 90, 1000);
         let end = create_basic_twap_message(i128::MAX, 200, 180, 1100);
 
+        validate_twap_messages(&start, &end).unwrap();
         let err = calculate_twap(&start, &end).unwrap_err();
         assert_eq!(err, ReceiverError::TwapCalculationOverflow.into());
     }
+
+    #[test]
+    fn test_mismatched_feed_id() {
+        let start = create_basic_twap_message(100, 100, 90, 1000);
+        let mut end = create_basic_twap_message(300, 200, 180, 1100);
+        end.feed_id = [1; 32];
+
+        let err = validate_twap_messages(&start, &end).unwrap_err();
+        assert_eq!(err, ReceiverError::FeedIdMismatch.into());
+    }
+
+    #[test]
+    fn test_mismatched_exponent() {
+        let start = create_basic_twap_message(100, 100, 90, 1000);
+        let mut end = create_basic_twap_message(300, 200, 180, 1100);
+        end.exponent = 9;
+
+        let err = validate_twap_messages(&start, &end).unwrap_err();
+        assert_eq!(err, ReceiverError::ExponentMismatch.into());
+    }
 }

+ 1 - 9
target_chains/solana/programs/pyth-solana-receiver/tests/test_post_twap_updates.rs

@@ -6,7 +6,7 @@ use {
         instruction::PostTwapUpdate,
         sdk::{deserialize_accumulator_update_data, DEFAULT_TREASURY_ID},
     },
-    pyth_solana_receiver_sdk::price_update::{TwapUpdate, VerificationLevel},
+    pyth_solana_receiver_sdk::price_update::TwapUpdate,
     pythnet_sdk::{
         messages::{Message, TwapMessage},
         test_utils::create_accumulator_message,
@@ -161,10 +161,6 @@ async fn test_post_twap_updates() {
 
     // Assert that the TWAP account was created correctly
     assert_eq!(twap_update_account_1.write_authority, poster.pubkey());
-    assert_eq!(
-        twap_update_account_1.verification_level,
-        VerificationLevel::Full
-    );
 
     // Assert all TWAP fields are correctly calculated for feed 1
     assert_eq!(twap_update_account_1.twap.feed_id, [1; 32]);
@@ -183,10 +179,6 @@ async fn test_post_twap_updates() {
 
     // Assert that the TWAP account was created correctly
     assert_eq!(twap_update_account_2.write_authority, poster.pubkey());
-    assert_eq!(
-        twap_update_account_2.verification_level,
-        VerificationLevel::Full
-    );
 
     // Assert all TWAP fields are correctly calculated for feed 2
     assert_eq!(twap_update_account_2.twap.feed_id, [2; 32]);

+ 15 - 46
target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs

@@ -59,29 +59,27 @@ pub struct PriceUpdateV2 {
 impl PriceUpdateV2 {
     pub const LEN: usize = 8 + 32 + 2 + 32 + 8 + 8 + 4 + 8 + 8 + 8 + 8 + 8;
 }
-/// A time weighted average price account. This account is used by the Pyth Receiver program to store a verified TWAP update from a Pyth price feed.
+/// A time weighted average price account.
+/// This account is used by the Pyth Receiver program to store a TWAP update from a Pyth price feed.
+/// TwapUpdates can only be created after the client has verified the VAAs via the Wormhole contract.
+/// Check out `target_chains/solana/cli/src/main.rs` for an example of how to do this.
+///
 /// It contains:
 /// - `write_authority`: The write authority for this account. This authority can close this account to reclaim rent or update the account to contain a different TWAP update.
-/// - `verification_level`: The [`VerificationLevel`] of this price update. This represents how many Wormhole guardian signatures have been verified for this TWAP update.
 /// - `twap`: The actual TWAP update.
-/// - `posted_slot`: The slot at which this TWAP update was posted.
 #[account]
 #[derive(BorshSchema)]
 pub struct TwapUpdate {
     pub write_authority: Pubkey,
-    pub verification_level: VerificationLevel,
     pub twap: TwapPrice,
-    pub posted_slot: u64,
 }
 
 impl TwapUpdate {
     pub const LEN: usize = (
         8 // account discriminator (anchor)
         + 32 // write_authority
-        + 2 // verification_level
-        + (32 + 8 + 8 + 8 + 8 + 4 + 4) // twap
-        + 8
-        // posted_slot
+        + (32 + 8 + 8 + 8 + 8 + 4 + 4)
+        // twap
     );
 
     /// Get a `TwapPrice` from a `TwapUpdate` account for a given `FeedId`.
@@ -104,7 +102,7 @@ impl TwapUpdate {
         Ok(self.twap)
     }
 
-    /// Get a `TwapPrice` from a `TwapUpdate` account for a given `FeedId` no older than `maximum_age` with `Full` verification.
+    /// Get a `TwapPrice` from a `TwapUpdate` account for a given `FeedId` no older than `maximum_age`.
     ///
     /// # Example
     /// ```
@@ -131,11 +129,6 @@ impl TwapUpdate {
         maximum_age: u64,
         feed_id: &FeedId,
     ) -> std::result::Result<TwapPrice, GetPriceError> {
-        // Ensure the update is fully verified
-        check!(
-            self.verification_level.eq(&VerificationLevel::Full),
-            GetPriceError::InsufficientVerificationLevel
-        );
         // Ensure the update isn't outdated
         let twap_price = self.get_twap_unchecked(feed_id)?;
         check!(
@@ -570,57 +563,33 @@ pub mod tests {
             ..Default::default()
         };
 
-        let twap_update_unverified = TwapUpdate {
+        let update = TwapUpdate {
             write_authority: Pubkey::new_unique(),
-            verification_level: VerificationLevel::Partial { num_signatures: 0 },
-            twap: expected_twap,
-            posted_slot: 0,
-        };
-
-        let twap_update_fully_verified = TwapUpdate {
-            write_authority: Pubkey::new_unique(),
-            verification_level: VerificationLevel::Full,
             twap: expected_twap,
-            posted_slot: 0,
         };
 
         // Test unchecked access
-        assert_eq!(
-            twap_update_unverified.get_twap_unchecked(&feed_id),
-            Ok(expected_twap)
-        );
-        assert_eq!(
-            twap_update_fully_verified.get_twap_unchecked(&feed_id),
-            Ok(expected_twap)
-        );
+        assert_eq!(update.get_twap_unchecked(&feed_id), Ok(expected_twap));
 
-        // Test with age and verification checks
+        // Test with age check
         assert_eq!(
-            twap_update_unverified.get_twap_no_older_than(&mock_clock, 100, &feed_id),
-            Err(GetPriceError::InsufficientVerificationLevel)
-        );
-        assert_eq!(
-            twap_update_fully_verified.get_twap_no_older_than(&mock_clock, 100, &feed_id),
+            update.get_twap_no_older_than(&mock_clock, 100, &feed_id),
             Ok(expected_twap)
         );
 
         // Test with reduced maximum age
         assert_eq!(
-            twap_update_fully_verified.get_twap_no_older_than(&mock_clock, 10, &feed_id),
+            update.get_twap_no_older_than(&mock_clock, 10, &feed_id),
             Err(GetPriceError::PriceTooOld)
         );
 
         // Test with mismatched feed id
         assert_eq!(
-            twap_update_fully_verified.get_twap_unchecked(&mismatched_feed_id),
+            update.get_twap_unchecked(&mismatched_feed_id),
             Err(GetPriceError::MismatchedFeedId)
         );
         assert_eq!(
-            twap_update_fully_verified.get_twap_no_older_than(
-                &mock_clock,
-                100,
-                &mismatched_feed_id
-            ),
+            update.get_twap_no_older_than(&mock_clock, 100, &mismatched_feed_id),
             Err(GetPriceError::MismatchedFeedId)
         );
     }