Skip to content

Commit a607335

Browse files
authored
[solana push oracle] Idempotent updates (#1452)
* idempotent updates * clippy
1 parent 8fba519 commit a607335

File tree

4 files changed

+60
-35
lines changed

4 files changed

+60
-35
lines changed

target_chains/solana/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

target_chains/solana/programs/pyth-push-oracle/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ test-bpf = []
1919
anchor-lang = { workspace = true }
2020
pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk" }
2121
solana-program = { workspace = true }
22+
byteorder = "1.4.3"
2223
pyth-solana-receiver-sdk = { path = "../../pyth_solana_receiver_sdk"}
2324
pyth-solana-receiver = { path = "../pyth-solana-receiver", features = ["cpi"]}
2425

target_chains/solana/programs/pyth-push-oracle/src/lib.rs

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ use {
99
price_update::PriceUpdateV2,
1010
PYTH_PUSH_ORACLE_ID,
1111
},
12-
pythnet_sdk::messages::FeedId,
12+
pythnet_sdk::{
13+
messages::{
14+
FeedId,
15+
Message,
16+
},
17+
wire::from_slice,
18+
},
1319
};
1420

1521
pub mod sdk;
@@ -22,6 +28,10 @@ pub enum PushOracleError {
2228
UpdatesNotMonotonic,
2329
#[msg("Trying to update price feed with the wrong feed id")]
2430
PriceFeedMessageMismatch,
31+
#[msg("The message in the update must be a PriceFeedMessage")]
32+
UnsupportedMessageType,
33+
#[msg("Could not deserialize the message in the update")]
34+
DeserializeMessageFailed,
2535
}
2636
#[program]
2737
pub mod pyth_push_oracle {
@@ -53,7 +63,7 @@ pub mod pyth_push_oracle {
5363
let signer_seeds = &[&seeds[..]];
5464
let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);
5565

56-
66+
// Get the timestamp of the price currently stored in the price feed account.
5767
let current_timestamp = {
5868
if ctx.accounts.price_feed_account.data_is_empty() {
5969
0
@@ -64,20 +74,37 @@ pub mod pyth_push_oracle {
6474
price_feed_account.price_message.publish_time
6575
}
6676
};
67-
pyth_solana_receiver::cpi::post_update(cpi_context, params)?;
68-
{
69-
let price_feed_account_data = ctx.accounts.price_feed_account.try_borrow_data()?;
70-
let price_feed_account =
71-
PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;
7277

73-
require!(
74-
price_feed_account.price_message.publish_time > current_timestamp,
75-
PushOracleError::UpdatesNotMonotonic
76-
);
77-
require!(
78-
price_feed_account.price_message.feed_id == feed_id,
79-
PushOracleError::PriceFeedMessageMismatch
80-
);
78+
// Get the timestamp of the price in the arguments (that we are trying to put in the account).
79+
// It is a little annoying that we have to redundantly deserialize the message here, but
80+
// it is required to make txs pushing stale prices succeed w/o updating the on-chain price.
81+
//
82+
// Note that we don't do any validity checks on the proof etc. here. If the caller passes an
83+
// invalid message with a newer timestamp, the validity checks will be performed by pyth_solana_receiver.
84+
let message =
85+
from_slice::<byteorder::BE, Message>(params.merkle_price_update.message.as_ref())
86+
.map_err(|_| PushOracleError::DeserializeMessageFailed)?;
87+
let next_timestamp = match message {
88+
Message::PriceFeedMessage(price_feed_message) => price_feed_message.publish_time,
89+
Message::TwapMessage(_) => {
90+
return err!(PushOracleError::UnsupportedMessageType);
91+
}
92+
};
93+
94+
// Only update the price feed if the message contains a newer price. Pushing a stale price
95+
// suceeds without changing the on-chain state.
96+
if next_timestamp > current_timestamp {
97+
pyth_solana_receiver::cpi::post_update(cpi_context, params)?;
98+
{
99+
let price_feed_account_data = ctx.accounts.price_feed_account.try_borrow_data()?;
100+
let price_feed_account =
101+
PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;
102+
103+
require!(
104+
price_feed_account.price_message.feed_id == feed_id,
105+
PushOracleError::PriceFeedMessageMismatch
106+
);
107+
}
81108
}
82109
Ok(())
83110
}

target_chains/solana/programs/pyth-push-oracle/tests/test_update_price_feed.rs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -161,26 +161,22 @@ async fn test_update_price_feed() {
161161
program_simulator.get_clock().await.unwrap().slot
162162
);
163163

164-
// post another update, outdated
165-
assert_eq!(
166-
program_simulator
167-
.process_ix_with_default_compute_limit(
168-
UpdatePriceFeed::populate(
169-
poster.pubkey(),
170-
encoded_vaa_addresses[0],
171-
DEFAULT_SHARD,
172-
feed_id,
173-
DEFAULT_TREASURY_ID,
174-
merkle_price_updates[1].clone(),
175-
),
176-
&vec![&poster],
177-
None,
178-
)
179-
.await
180-
.unwrap_err()
181-
.unwrap(),
182-
into_transaction_error(PushOracleError::UpdatesNotMonotonic)
183-
);
164+
// post a stale update. The tx succeeds w/o updating on-chain account state.
165+
program_simulator
166+
.process_ix_with_default_compute_limit(
167+
UpdatePriceFeed::populate(
168+
poster.pubkey(),
169+
encoded_vaa_addresses[0],
170+
DEFAULT_SHARD,
171+
feed_id,
172+
DEFAULT_TREASURY_ID,
173+
merkle_price_updates[0].clone(),
174+
),
175+
&vec![&poster],
176+
None,
177+
)
178+
.await
179+
.unwrap();
184180

185181
assert_treasury_balance(
186182
&mut program_simulator,

0 commit comments

Comments
 (0)