Skip to content

Commit bbce7fa

Browse files
committed
Merge branch 'main' of github.com:pyth-network/pyth-crosschain into tb/pulse-scheduler/better-subparams-validation
2 parents b98f257 + ca1200a commit bbce7fa

File tree

12 files changed

+232
-42
lines changed

12 files changed

+232
-42
lines changed

.github/workflows/ci-lazer-solana-contract.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- uses: actions/checkout@v4
2020
- uses: actions-rust-lang/setup-rust-toolchain@v1
2121
with:
22-
toolchain: 1.81.0
22+
toolchain: 1.82.0
2323
- name: install taplo
2424
run: cargo install --locked [email protected]
2525
- uses: actions/setup-node@v4
@@ -45,6 +45,7 @@ jobs:
4545
- name: Install Anchor
4646
run: |
4747
rustup install 1.79.0
48+
rustup install nightly-2025-04-15
4849
RUSTFLAGS= cargo +1.79.0 install --git https://github.com/coral-xyz/anchor --tag v0.30.1 --locked anchor-cli
4950
- name: Run anchor tests
5051
run: pnpm run test:anchor

lazer/Cargo.lock

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

lazer/contracts/solana/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"scripts": {
66
"fix:format": "prettier --write **/*.*",
77
"test:format": "prettier --check **/*.*",
8-
"test:anchor": "CARGO_TARGET_DIR=\"$PWD/target\" anchor test",
8+
"test:anchor": "CARGO_TARGET_DIR=\"$PWD/target\" RUSTUP_TOOLCHAIN=nightly-2025-04-15 anchor test",
99
"setup": "pnpm ts-node scripts/setup.ts",
1010
"check-trusted-signer": "pnpm ts-node scripts/check_trusted_signer.ts"
1111
},

lazer/sdk/rust/protocol/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyth-lazer-protocol"
3-
version = "0.7.0"
3+
version = "0.7.1"
44
edition = "2021"
55
description = "Pyth Lazer SDK - protocol types."
66
license = "Apache-2.0"

lazer/sdk/rust/protocol/src/api.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::router::{
44
Channel, Format, JsonBinaryEncoding, JsonUpdate, PriceFeedId, PriceFeedProperty, TimestampUs,
55
};
66

7-
#[derive(Debug, Clone, Serialize, Deserialize)]
7+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
88
#[serde(rename_all = "camelCase")]
99
pub struct LatestPriceRequest {
1010
pub price_feed_ids: Vec<PriceFeedId>,
@@ -21,7 +21,7 @@ pub struct LatestPriceRequest {
2121
pub channel: Channel,
2222
}
2323

24-
#[derive(Debug, Clone, Serialize, Deserialize)]
24+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
2525
#[serde(rename_all = "camelCase")]
2626
pub struct PriceRequest {
2727
pub timestamp: TimestampUs,
@@ -37,7 +37,7 @@ pub struct PriceRequest {
3737
pub channel: Channel,
3838
}
3939

40-
#[derive(Debug, Clone, Serialize, Deserialize)]
40+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
4141
#[serde(rename_all = "camelCase")]
4242
pub struct ReducePriceRequest {
4343
pub payload: JsonUpdate,

lazer/sdk/rust/protocol/src/payload.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub enum PayloadPropertyValue {
4040
FundingTimestamp(Option<TimestampUs>),
4141
}
4242

43-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
43+
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
4444
pub struct AggregatedPriceFeedData {
4545
pub price: Option<Price>,
4646
pub best_bid_price: Option<Price>,

lazer/sdk/rust/protocol/src/publisher.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use {
1010

1111
/// Represents a binary (bincode-serialized) stream update sent
1212
/// from the publisher to the router.
13-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1414
#[serde(rename_all = "camelCase")]
1515
pub struct PriceFeedDataV2 {
1616
pub price_feed_id: PriceFeedId,
@@ -36,7 +36,7 @@ pub struct PriceFeedDataV2 {
3636
/// Old Represents a binary (bincode-serialized) stream update sent
3737
/// from the publisher to the router.
3838
/// Superseded by `PriceFeedData`.
39-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
4040
#[serde(rename_all = "camelCase")]
4141
pub struct PriceFeedDataV1 {
4242
pub price_feed_id: PriceFeedId,
@@ -75,14 +75,14 @@ impl From<PriceFeedDataV1> for PriceFeedDataV2 {
7575

7676
/// A response sent from the server to the publisher client.
7777
/// Currently only serde errors are reported back to the client.
78-
#[derive(Debug, Clone, Serialize, Deserialize, From)]
78+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, From)]
7979
#[serde(tag = "type")]
8080
#[serde(rename_all = "camelCase")]
8181
pub enum ServerResponse {
8282
UpdateDeserializationError(UpdateDeserializationErrorResponse),
8383
}
8484
/// Sent to the publisher if the binary data could not be parsed
85-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8686
#[serde(rename_all = "camelCase")]
8787
pub struct UpdateDeserializationErrorResponse {
8888
pub error: String,

lazer/sdk/rust/protocol/src/router.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ use {
1414
},
1515
};
1616

17-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
1818
pub struct PublisherId(pub u16);
1919

20-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
20+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
2121
pub struct PriceFeedId(pub u32);
2222

23-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
2424
pub struct ChannelId(pub u8);
2525

2626
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]

lazer/sdk/rust/protocol/src/subscription.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,26 @@ use {
88
};
99

1010
/// A request sent from the client to the server.
11-
#[derive(Debug, Clone, Serialize, Deserialize)]
11+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1212
#[serde(tag = "type")]
1313
#[serde(rename_all = "camelCase")]
1414
pub enum Request {
1515
Subscribe(SubscribeRequest),
1616
Unsubscribe(UnsubscribeRequest),
1717
}
1818

19-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
2020
pub struct SubscriptionId(pub u64);
2121

22-
#[derive(Debug, Clone, Serialize, Deserialize)]
22+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
2323
#[serde(rename_all = "camelCase")]
2424
pub struct SubscribeRequest {
2525
pub subscription_id: SubscriptionId,
2626
#[serde(flatten)]
2727
pub params: SubscriptionParams,
2828
}
2929

30-
#[derive(Debug, Clone, Serialize, Deserialize)]
30+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
3131
#[serde(rename_all = "camelCase")]
3232
pub struct UnsubscribeRequest {
3333
pub subscription_id: SubscriptionId,

target_chains/ethereum/contracts/contracts/pulse/scheduler/Scheduler.sol

Lines changed: 98 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,7 @@ abstract contract Scheduler is IScheduler, SchedulerState {
7373

7474
// Check for permanent subscription restrictions
7575
if (currentParams.isPermanent) {
76-
// Cannot disable isPermanent flag once set
77-
if (!newParams.isPermanent) {
78-
revert IllegalPermanentSubscriptionModification();
79-
}
80-
81-
// Cannot remove price feeds from a permanent subscription
82-
if (newParams.priceIds.length < currentParams.priceIds.length) {
83-
revert IllegalPermanentSubscriptionModification();
84-
}
85-
86-
// Check that all existing price IDs are preserved
87-
for (uint i = 0; i < currentParams.priceIds.length; i++) {
88-
bool found = false;
89-
for (uint j = 0; j < newParams.priceIds.length; j++) {
90-
if (currentParams.priceIds[i] == newParams.priceIds[j]) {
91-
found = true;
92-
break;
93-
}
94-
}
95-
if (!found) {
96-
revert IllegalPermanentSubscriptionModification();
97-
}
98-
}
76+
_validatePermanentSubscriptionUpdate(currentParams, newParams);
9977
}
10078

10179
// If subscription is inactive and will remain inactive, no need to validate parameters
@@ -437,6 +415,103 @@ abstract contract Scheduler is IScheduler, SchedulerState {
437415
revert UpdateConditionsNotMet();
438416
}
439417

418+
/**
419+
* @notice Internal helper to validate modifications to a permanent subscription.
420+
* @param currentParams The current subscription parameters (storage).
421+
* @param newParams The proposed new subscription parameters (memory).
422+
*/
423+
function _validatePermanentSubscriptionUpdate(
424+
SubscriptionParams storage currentParams,
425+
SubscriptionParams memory newParams
426+
) internal view {
427+
// Cannot disable isPermanent flag once set
428+
if (!newParams.isPermanent) {
429+
revert IllegalPermanentSubscriptionModification();
430+
}
431+
432+
// Cannot deactivate a permanent subscription
433+
if (!newParams.isActive) {
434+
revert IllegalPermanentSubscriptionModification();
435+
}
436+
437+
// Cannot remove price feeds from a permanent subscription
438+
if (newParams.priceIds.length < currentParams.priceIds.length) {
439+
revert IllegalPermanentSubscriptionModification();
440+
}
441+
442+
// Check that all existing price IDs are preserved (adding is allowed, not removing)
443+
for (uint i = 0; i < currentParams.priceIds.length; i++) {
444+
bool found = false;
445+
for (uint j = 0; j < newParams.priceIds.length; j++) {
446+
if (currentParams.priceIds[i] == newParams.priceIds[j]) {
447+
found = true;
448+
break;
449+
}
450+
}
451+
if (!found) {
452+
revert IllegalPermanentSubscriptionModification();
453+
}
454+
}
455+
456+
// Cannot change reader whitelist settings for permanent subscriptions
457+
if (newParams.whitelistEnabled != currentParams.whitelistEnabled) {
458+
revert IllegalPermanentSubscriptionModification();
459+
}
460+
461+
// Check if the set of addresses in the whitelist is the same
462+
if (
463+
newParams.readerWhitelist.length !=
464+
currentParams.readerWhitelist.length
465+
) {
466+
revert IllegalPermanentSubscriptionModification();
467+
}
468+
uint256 n = newParams.readerWhitelist.length;
469+
bool[] memory currentVisited = new bool[](n);
470+
uint256 matchesFound = 0;
471+
for (uint256 i = 0; i < n; i++) {
472+
bool foundInCurrent = false;
473+
for (uint256 j = 0; j < n; j++) {
474+
if (
475+
!currentVisited[j] &&
476+
newParams.readerWhitelist[i] ==
477+
currentParams.readerWhitelist[j]
478+
) {
479+
currentVisited[j] = true;
480+
foundInCurrent = true;
481+
matchesFound++;
482+
break;
483+
}
484+
}
485+
if (!foundInCurrent) {
486+
revert IllegalPermanentSubscriptionModification();
487+
}
488+
}
489+
490+
// Cannot change update criteria for permanent subscriptions
491+
if (
492+
newParams.updateCriteria.updateOnHeartbeat !=
493+
currentParams.updateCriteria.updateOnHeartbeat ||
494+
newParams.updateCriteria.heartbeatSeconds !=
495+
currentParams.updateCriteria.heartbeatSeconds ||
496+
newParams.updateCriteria.updateOnDeviation !=
497+
currentParams.updateCriteria.updateOnDeviation ||
498+
newParams.updateCriteria.deviationThresholdBps !=
499+
currentParams.updateCriteria.deviationThresholdBps
500+
) {
501+
revert IllegalPermanentSubscriptionModification();
502+
}
503+
504+
// Cannot change gas config for permanent subscriptions
505+
if (
506+
newParams.gasConfig.maxBaseFeeMultiplierCapPct !=
507+
currentParams.gasConfig.maxBaseFeeMultiplierCapPct ||
508+
newParams.gasConfig.maxPriorityFeeMultiplierCapPct !=
509+
currentParams.gasConfig.maxPriorityFeeMultiplierCapPct
510+
) {
511+
revert IllegalPermanentSubscriptionModification();
512+
}
513+
}
514+
440515
/// FETCH PRICES
441516

442517
/**

0 commit comments

Comments
 (0)