diff --git a/target_chains/solana/Cargo.lock b/target_chains/solana/Cargo.lock index 5c7368e910..69564db24d 100644 --- a/target_chains/solana/Cargo.lock +++ b/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", diff --git a/target_chains/solana/cli/Cargo.toml b/target_chains/solana/cli/Cargo.toml index 7efbfd4fd6..f1adf73b56 100644 --- a/target_chains/solana/cli/Cargo.toml +++ b/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" } diff --git a/target_chains/solana/cli/src/cli.rs b/target_chains/solana/cli/src/cli.rs index eea41a787a..fb19cc39a8 100644 --- a/target_chains/solana/cli/src/cli.rs +++ b/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, diff --git a/target_chains/solana/cli/src/main.rs b/target_chains/solana/cli/src/main.rs index 48ec19f02c..2d458a1ab8 100644 --- a/target_chains/solana/cli/src/main.rs +++ b/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 = base64::decode(start_payload)?; + let end_payload_bytes: Vec = 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 { 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 { + // 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> { 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> { + 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( diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs b/target_chains/solana/programs/pyth-solana-receiver/src/error.rs index 5e06a44ad0..61055debe1 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/error.rs +++ b/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, diff --git a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs b/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs index 32a0f9887e..d242f8bde9 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/src/lib.rs +++ b/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, 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()); + } } diff --git a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_twap_updates.rs b/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_twap_updates.rs index 162e06cd33..bf06f356e8 100644 --- a/target_chains/solana/programs/pyth-solana-receiver/tests/test_post_twap_updates.rs +++ b/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]); diff --git a/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs b/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs index 8433bf277c..79741b66cb 100644 --- a/target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs +++ b/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 { - // 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) ); }