[solana push oracle] Idempotent updates (#1452)

* idempotent updates

* clippy
This commit is contained in:
Jayant Krishnamurthy 2024-04-12 14:37:25 -07:00 committed by GitHub
parent 8fba519ce3
commit a60733559c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 35 deletions

View File

@ -3019,6 +3019,7 @@ name = "pyth-push-oracle"
version = "0.1.0"
dependencies = [
"anchor-lang",
"byteorder",
"common-test-utils",
"program-simulator",
"pyth-solana-receiver",

View File

@ -19,6 +19,7 @@ test-bpf = []
anchor-lang = { workspace = true }
pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk" }
solana-program = { workspace = true }
byteorder = "1.4.3"
pyth-solana-receiver-sdk = { path = "../../pyth_solana_receiver_sdk"}
pyth-solana-receiver = { path = "../pyth-solana-receiver", features = ["cpi"]}

View File

@ -9,7 +9,13 @@ use {
price_update::PriceUpdateV2,
PYTH_PUSH_ORACLE_ID,
},
pythnet_sdk::messages::FeedId,
pythnet_sdk::{
messages::{
FeedId,
Message,
},
wire::from_slice,
},
};
pub mod sdk;
@ -22,6 +28,10 @@ pub enum PushOracleError {
UpdatesNotMonotonic,
#[msg("Trying to update price feed with the wrong feed id")]
PriceFeedMessageMismatch,
#[msg("The message in the update must be a PriceFeedMessage")]
UnsupportedMessageType,
#[msg("Could not deserialize the message in the update")]
DeserializeMessageFailed,
}
#[program]
pub mod pyth_push_oracle {
@ -53,7 +63,7 @@ pub mod pyth_push_oracle {
let signer_seeds = &[&seeds[..]];
let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);
// Get the timestamp of the price currently stored in the price feed account.
let current_timestamp = {
if ctx.accounts.price_feed_account.data_is_empty() {
0
@ -64,20 +74,37 @@ pub mod pyth_push_oracle {
price_feed_account.price_message.publish_time
}
};
pyth_solana_receiver::cpi::post_update(cpi_context, params)?;
{
let price_feed_account_data = ctx.accounts.price_feed_account.try_borrow_data()?;
let price_feed_account =
PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;
require!(
price_feed_account.price_message.publish_time > current_timestamp,
PushOracleError::UpdatesNotMonotonic
);
require!(
price_feed_account.price_message.feed_id == feed_id,
PushOracleError::PriceFeedMessageMismatch
);
// Get the timestamp of the price in the arguments (that we are trying to put in the account).
// It is a little annoying that we have to redundantly deserialize the message here, but
// it is required to make txs pushing stale prices succeed w/o updating the on-chain price.
//
// Note that we don't do any validity checks on the proof etc. here. If the caller passes an
// invalid message with a newer timestamp, the validity checks will be performed by pyth_solana_receiver.
let message =
from_slice::<byteorder::BE, Message>(params.merkle_price_update.message.as_ref())
.map_err(|_| PushOracleError::DeserializeMessageFailed)?;
let next_timestamp = match message {
Message::PriceFeedMessage(price_feed_message) => price_feed_message.publish_time,
Message::TwapMessage(_) => {
return err!(PushOracleError::UnsupportedMessageType);
}
};
// Only update the price feed if the message contains a newer price. Pushing a stale price
// suceeds without changing the on-chain state.
if next_timestamp > current_timestamp {
pyth_solana_receiver::cpi::post_update(cpi_context, params)?;
{
let price_feed_account_data = ctx.accounts.price_feed_account.try_borrow_data()?;
let price_feed_account =
PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;
require!(
price_feed_account.price_message.feed_id == feed_id,
PushOracleError::PriceFeedMessageMismatch
);
}
}
Ok(())
}

View File

@ -161,26 +161,22 @@ async fn test_update_price_feed() {
program_simulator.get_clock().await.unwrap().slot
);
// post another update, outdated
assert_eq!(
program_simulator
.process_ix_with_default_compute_limit(
UpdatePriceFeed::populate(
poster.pubkey(),
encoded_vaa_addresses[0],
DEFAULT_SHARD,
feed_id,
DEFAULT_TREASURY_ID,
merkle_price_updates[1].clone(),
),
&vec![&poster],
None,
)
.await
.unwrap_err()
.unwrap(),
into_transaction_error(PushOracleError::UpdatesNotMonotonic)
);
// post a stale update. The tx succeeds w/o updating on-chain account state.
program_simulator
.process_ix_with_default_compute_limit(
UpdatePriceFeed::populate(
poster.pubkey(),
encoded_vaa_addresses[0],
DEFAULT_SHARD,
feed_id,
DEFAULT_TREASURY_ID,
merkle_price_updates[0].clone(),
),
&vec![&poster],
None,
)
.await
.unwrap();
assert_treasury_balance(
&mut program_simulator,