[solana push oracle] Idempotent updates (#1452)
* idempotent updates * clippy
This commit is contained in:
parent
8fba519ce3
commit
a60733559c
|
@ -3019,6 +3019,7 @@ name = "pyth-push-oracle"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"byteorder",
|
||||
"common-test-utils",
|
||||
"program-simulator",
|
||||
"pyth-solana-receiver",
|
||||
|
|
|
@ -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"]}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue