Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions program/c/src/oracle/oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,9 @@ typedef struct pc_price
pc_ema_t twac_; // time-weighted average conf interval
int64_t timestamp_; // unix timestamp of aggregate price
uint8_t min_pub_; // min publishers for valid price
int8_t drv2_; // space for future derived values
int16_t drv3_; // space for future derived values
int8_t message_sent_; // flag to indicate if the current aggregate price has been sent as a message to the message buffer, 0 if not sent, 1 if sent
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drive-by: this was already changed in the rust program previously but wasn't changed here

uint8_t max_latency_; // configurable max latency in slots between send and receive
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only need uint8_t for max_latency_ so splitting it to uint8_t and int8_t

int8_t drv3_; // space for future derived values
int32_t drv4_; // space for future derived values
pc_pub_key_t prod_; // product id/ref-account
pc_pub_key_t next_; // next price account in list
Expand Down
4 changes: 3 additions & 1 deletion program/c/src/oracle/upd_aggregate.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,13 @@ static inline bool upd_aggregate( pc_price_t *ptr, uint64_t slot, int64_t timest
int64_t slot_diff = ( int64_t )slot - ( int64_t )( iptr->agg_.pub_slot_ );
int64_t price = iptr->agg_.price_;
int64_t conf = ( int64_t )( iptr->agg_.conf_ );
int64_t max_latency = ptr->max_latency_ ? ptr->max_latency_ : PC_MAX_SEND_LATENCY;
if ( iptr->agg_.status_ == PC_STATUS_TRADING &&
// No overflow for INT64_MIN+conf or INT64_MAX-conf as 0 < conf < INT64_MAX
// These checks ensure that price - conf and price + conf do not overflow.
(int64_t)0 < conf && (INT64_MIN + conf) <= price && price <= (INT64_MAX-conf) &&
slot_diff >= 0 && slot_diff <= PC_MAX_SEND_LATENCY ) {
// slot_diff is implicitly >= 0 due to the check in Rust code ensuring publishing_slot is always less than or equal to the current slot.
slot_diff <= max_latency ) {
numv += 1;
prcs[ nprcs++ ] = price - conf;
prcs[ nprcs++ ] = price;
Expand Down
2 changes: 1 addition & 1 deletion program/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pyth-oracle"
version = "2.25.0"
version = "2.26.0"
edition = "2021"
license = "Apache 2.0"
publish = false
Expand Down
21 changes: 17 additions & 4 deletions program/rust/src/accounts/price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ mod price_pythnet {
/// Minimum valid publisher quotes for a succesful aggregation
pub min_pub_: u8,
pub message_sent_: u8,
pub unused_2_: i16,
/// Configurable max latency in slots between send and receive
pub max_latency_: u8,
/// Unused placeholder for alignment
pub unused_2_: i8,
pub unused_3_: i32,
/// Corresponding product account
pub product_account: Pubkey,
Expand Down Expand Up @@ -116,6 +119,7 @@ mod price_pythnet {
self.agg_.price_,
self.agg_.conf_,
self.agg_.pub_slot_.saturating_sub(self.prev_slot_),
self.max_latency_,
); // pub_slot should always be >= prev_slot, but we protect ourselves against underflow just in case
Ok(())
} else {
Expand Down Expand Up @@ -172,11 +176,17 @@ mod price_pythnet {
}

impl PriceCumulative {
pub fn update(&mut self, price: i64, conf: u64, slot_gap: u64) {
pub fn update(&mut self, price: i64, conf: u64, slot_gap: u64, max_latency: u8) {
self.price += i128::from(price) * i128::from(slot_gap);
self.conf += u128::from(conf) * u128::from(slot_gap);
// Use PC_MAX_SEND_LATENCY if max_latency is 0, otherwise use max_latency
let latency = if max_latency == 0 {
u64::from(PC_MAX_SEND_LATENCY)
} else {
u64::from(max_latency)
};
// This is expected to saturate at 0 most of the time (while the feed is up).
self.num_down_slots += slot_gap.saturating_sub(PC_MAX_SEND_LATENCY.into());
self.num_down_slots += slot_gap.saturating_sub(latency);
}
}
}
Expand Down Expand Up @@ -225,7 +235,10 @@ mod price_solana {
/// Whether the current aggregate price has been sent as a message to the message buffer.
/// 0 = false, 1 = true. (this is a u8 to make the Pod trait happy)
pub message_sent_: u8,
pub unused_2_: i16,
/// Configurable max latency in slots between send and receive
pub max_latency_: u8,
/// Unused placeholder for alignment
pub unused_2_: i8,
pub unused_3_: i32,
/// Corresponding product account
pub product_account: Pubkey,
Expand Down
127 changes: 99 additions & 28 deletions program/rust/src/tests/test_twap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ use {

#[derive(Clone, Debug, Copy)]
pub struct DataEvent {
price: i64,
conf: u64,
slot_gap: u64,
price: i64,
conf: u64,
slot_gap: u64,
max_latency: u8,
}

impl Arbitrary for DataEvent {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
DataEvent {
slot_gap: u64::from(u8::arbitrary(g)) + 1, /* Slot gap is always > 1, because there
* has been a succesful aggregation */
price: i64::arbitrary(g),
conf: u64::arbitrary(g),
slot_gap: u64::from(u8::arbitrary(g)) + 1, /* Slot gap is always > 1, because there
* has been a succesful aggregation */
price: i64::arbitrary(g),
conf: u64::arbitrary(g),
max_latency: u8::arbitrary(g),
}
}
}
Expand All @@ -44,6 +46,7 @@ impl Arbitrary for DataEvent {
/// - slot_gap is a random number between 1 and u8::MAX + 1 (256)
/// - price is a random i64
/// - conf is a random u64
/// - max_latency is a random u8
#[quickcheck]
fn test_twap(input: Vec<DataEvent>) -> bool {
let mut price_cumulative = PriceCumulative {
Expand All @@ -56,7 +59,12 @@ fn test_twap(input: Vec<DataEvent>) -> bool {
let mut data = Vec::<DataEvent>::new();

for data_event in input {
price_cumulative.update(data_event.price, data_event.conf, data_event.slot_gap);
price_cumulative.update(
data_event.price,
data_event.conf,
data_event.slot_gap,
data_event.max_latency,
);
data.push(data_event);
price_cumulative.check_price(data.as_slice());
price_cumulative.check_conf(data.as_slice());
Expand All @@ -67,7 +75,6 @@ fn test_twap(input: Vec<DataEvent>) -> bool {
true
}


impl PriceCumulative {
pub fn check_price(&self, data: &[DataEvent]) {
assert_eq!(
Expand All @@ -87,12 +94,18 @@ impl PriceCumulative {
}
pub fn check_num_down_slots(&self, data: &[DataEvent]) {
assert_eq!(
data.iter()
.fold(0, |acc, x| if x.slot_gap > PC_MAX_SEND_LATENCY.into() {
acc + (x.slot_gap - PC_MAX_SEND_LATENCY as u64)
data.iter().fold(0, |acc, x| {
let latency_threshold = if x.max_latency == 0 {
PC_MAX_SEND_LATENCY.into()
} else {
x.max_latency.into()
};
if x.slot_gap > latency_threshold {
acc + (x.slot_gap - latency_threshold)
} else {
acc
}),
}
}),
self.num_down_slots
);
}
Expand All @@ -112,35 +125,65 @@ fn test_twap_unit() {

let data = vec![
DataEvent {
price: 1,
conf: 2,
slot_gap: 4,
price: 1,
conf: 2,
slot_gap: 4,
max_latency: 0,
},
DataEvent {
price: i64::MAX,
conf: u64::MAX,
slot_gap: 1,
max_latency: 0,
},
DataEvent {
price: i64::MAX,
conf: u64::MAX,
slot_gap: 1,
price: -10,
conf: 4,
slot_gap: 30,
max_latency: 0,
},
DataEvent {
price: -10,
conf: 4,
slot_gap: 30,
price: 1,
conf: 2,
slot_gap: 4,
max_latency: 5,
},
DataEvent {
price: 6,
conf: 7,
slot_gap: 8,
max_latency: 5,
},
];

price_cumulative.update(data[0].price, data[0].conf, data[0].slot_gap);
price_cumulative.update(
data[0].price,
data[0].conf,
data[0].slot_gap,
data[0].max_latency,
);
assert_eq!(price_cumulative.price, 5);
assert_eq!(price_cumulative.conf, 10);
assert_eq!(price_cumulative.num_down_slots, 3);
assert_eq!(price_cumulative.unused, 0);

price_cumulative.update(data[1].price, data[1].conf, data[1].slot_gap);
price_cumulative.update(
data[1].price,
data[1].conf,
data[1].slot_gap,
data[1].max_latency,
);
assert_eq!(price_cumulative.price, 9_223_372_036_854_775_812i128);
assert_eq!(price_cumulative.conf, 18_446_744_073_709_551_625u128);
assert_eq!(price_cumulative.num_down_slots, 3);
assert_eq!(price_cumulative.unused, 0);

price_cumulative.update(data[2].price, data[2].conf, data[2].slot_gap);
price_cumulative.update(
data[2].price,
data[2].conf,
data[2].slot_gap,
data[2].max_latency,
);
assert_eq!(price_cumulative.price, 9_223_372_036_854_775_512i128);
assert_eq!(price_cumulative.conf, 18_446_744_073_709_551_745u128);
assert_eq!(price_cumulative.num_down_slots, 8);
Expand All @@ -152,7 +195,7 @@ fn test_twap_unit() {
num_down_slots: 0,
unused: 0,
};
price_cumulative_overflow.update(i64::MIN, u64::MAX, u64::MAX);
price_cumulative_overflow.update(i64::MIN, u64::MAX, u64::MAX, u8::MAX);
assert_eq!(
price_cumulative_overflow.price,
i128::MIN - i128::from(i64::MIN)
Expand All @@ -163,9 +206,38 @@ fn test_twap_unit() {
);
assert_eq!(
price_cumulative_overflow.num_down_slots,
u64::MAX - PC_MAX_SEND_LATENCY as u64
u64::MAX - u64::from(u8::MAX)
);
assert_eq!(price_cumulative_overflow.unused, 0);

let mut price_cumulative_nonzero_max_latency = PriceCumulative {
price: 1,
conf: 2,
num_down_slots: 3,
unused: 0,
};

price_cumulative_nonzero_max_latency.update(
data[3].price,
data[3].conf,
data[3].slot_gap,
data[3].max_latency,
);
assert_eq!(price_cumulative_nonzero_max_latency.price, 5);
assert_eq!(price_cumulative_nonzero_max_latency.conf, 10);
assert_eq!(price_cumulative_nonzero_max_latency.num_down_slots, 3);
assert_eq!(price_cumulative_nonzero_max_latency.unused, 0);

price_cumulative_nonzero_max_latency.update(
data[4].price,
data[4].conf,
data[4].slot_gap,
data[4].max_latency,
);
assert_eq!(price_cumulative_nonzero_max_latency.price, 53);
assert_eq!(price_cumulative_nonzero_max_latency.conf, 66);
assert_eq!(price_cumulative_nonzero_max_latency.num_down_slots, 6);
assert_eq!(price_cumulative_nonzero_max_latency.unused, 0);
}

#[test]
Expand Down Expand Up @@ -224,7 +296,6 @@ fn test_twap_with_price_account() {
Err(OracleError::NeedsSuccesfulAggregation)
);


assert_eq!(price_data.price_cumulative.price, 1 - 2 * 10);
assert_eq!(price_data.price_cumulative.conf, 2 + 2 * 5);
assert_eq!(price_data.price_cumulative.num_down_slots, 3);
Expand Down
Loading