Skip to content
Merged

Sma3 #288

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d95623f
Pseudocode
guibescos Aug 30, 2022
e2b4739
Pseudocode
guibescos Aug 30, 2022
6d54f2e
Merge branch 'main' of https://github.com/pyth-network/pyth-client
guibescos Aug 31, 2022
ee9686a
Check
guibescos Sep 1, 2022
d5fdb7d
Wrote tests
guibescos Sep 2, 2022
424997c
some comments
guibescos Sep 2, 2022
7fb12c2
Cleanup
guibescos Sep 2, 2022
b5f8a51
Should pass CI
guibescos Sep 2, 2022
ce8138d
Starting tests
guibescos Sep 2, 2022
da1a4ec
Merge
guibescos Sep 2, 2022
be2f5e0
Update python tests
guibescos Sep 2, 2022
32d8515
Test upd sma
guibescos Sep 2, 2022
7276986
Another test
guibescos Sep 6, 2022
7973d07
More comments
guibescos Sep 6, 2022
af5407b
New implementation
guibescos Sep 7, 2022
444c685
Cleanup
guibescos Sep 7, 2022
62144b0
Add comment
guibescos Sep 7, 2022
ca5a82a
Merge branch 'main' into sma-2
guibescos Sep 7, 2022
d0738d5
Renames
guibescos Sep 7, 2022
eeb0fef
Cleanup
guibescos Sep 7, 2022
3de4bd9
Cleanup
guibescos Sep 7, 2022
d71c0f6
New implementation
guibescos Sep 7, 2022
d5956c3
New implementation
guibescos Sep 7, 2022
032f7b9
Comments
guibescos Sep 7, 2022
9880fe3
Some cleanup
guibescos Sep 7, 2022
0d718ce
Gate everything by test flags
guibescos Sep 7, 2022
bec400e
PR feedback
guibescos Sep 12, 2022
9515495
Fix comments
guibescos Sep 12, 2022
35057cc
Feedback
guibescos Sep 12, 2022
cb129fe
Add comment
guibescos Sep 12, 2022
486725c
First epoch is invalid always
guibescos Sep 12, 2022
fa6e697
Remove files
guibescos Sep 12, 2022
f530129
Comment test flags
guibescos Sep 12, 2022
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
4 changes: 2 additions & 2 deletions program/c/src/oracle/oracle.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ extern "C" {

// The size of the "time machine" account defined in the
// Rust portion of the codebase.
const uint64_t TIME_MACHINE_STRUCT_SIZE = 1864ULL;
const uint64_t TIME_MACHINE_STRUCT_SIZE = 1200ULL;

const uint64_t EXTRA_PUBLISHER_SPACE = 1000ULL;
const uint64_t EXTRA_PUBLISHER_SPACE = 3072ULL;


// magic number at head of account
Expand Down
2 changes: 2 additions & 0 deletions program/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ solana-program-test = "=1.10.29"
solana-sdk = "=1.10.29"
tokio = "1.14.1"
hex = "0.3.1"
quickcheck = "1"
quickcheck_macros = "1"

[features]
debug = []
Expand Down
3 changes: 3 additions & 0 deletions program/rust/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ mod test_init_price;
mod test_resize_account;
mod test_set_min_pub;
mod test_sizes;
mod test_sma;
mod test_sma_epoch_transition;
mod test_upd_aggregate;
mod test_upd_permissions;
mod test_upd_price;
mod test_upd_price_no_fail_on_error;
mod test_upd_product;
mod test_upd_sma;
mod test_utils;
6 changes: 6 additions & 0 deletions program/rust/src/tests/test_resize_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ async fn test_resize_account() {
assert!(sim.resize_price_account(&price1).await.is_ok());
let price1_account = sim.get_account(price1.pubkey()).await.unwrap();
assert_eq!(price1_account.data.len(), size_of::<PriceAccountWrapper>());
let price1_account_data = sim
.get_account_data_as::<PriceAccountWrapper>(price1.pubkey())
.await
.unwrap();
assert_eq!(price1_account_data.time_machine.granularity, 0);
assert_eq!(price1_account_data.time_machine.threshold, 0);
}
179 changes: 179 additions & 0 deletions program/rust/src/tests/test_sma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use quickcheck::Arbitrary;
use quickcheck_macros::quickcheck;

use crate::time_machine_types::{
DataPoint,
SmaTracker,
NUM_BUCKETS_THIRTY_MIN,
};

#[derive(Clone, Debug, Copy)]
struct DataEvent {
time_gap: i64,
slot_gap: u64,
price: i64,
}

impl Arbitrary for DataEvent {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
DataEvent {
time_gap: i64::from(u8::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),
}
}
}

/// This is a generative test for the sma struct. quickcheck will generate a series of
/// vectors of DataEvents of different length. The generation is based on the arbitrary trait
/// above.
/// For each DataEvent :
/// - time_gap is a random number between 0 and u8::MAX (255)
/// - slot_gap is a random number between 1 and u8::MAX + 1 (256)
/// - price is a random i64
#[quickcheck]
fn test_sma(input: Vec<DataEvent>) -> bool {
// No gaps, no skipped epochs
let mut tracker1 = SmaTracker::<NUM_BUCKETS_THIRTY_MIN>::zero();
tracker1.initialize(i64::from(u8::MAX), u64::from(u8::MAX));

// Skipped and gaps
let mut tracker2 = SmaTracker::<NUM_BUCKETS_THIRTY_MIN>::zero();
tracker2.initialize(i64::from(u8::MAX / 5), u64::from(u8::MAX / 5));

// Gaps, no skips
let mut tracker3 = SmaTracker::<NUM_BUCKETS_THIRTY_MIN>::zero();
tracker3.initialize(i64::from(u8::MAX), u64::from(u8::MAX / 5));

// No skips, gaps
let mut tracker4 = SmaTracker::<NUM_BUCKETS_THIRTY_MIN>::zero();
tracker4.initialize(i64::from(u8::MAX), u64::from(u8::MAX / 5) * 4);

// Each epoch is 1 second
let mut tracker5 = SmaTracker::<NUM_BUCKETS_THIRTY_MIN>::zero();
tracker5.initialize(1, u64::from(u8::MAX / 5));

let mut data = Vec::<DataPoint>::new();

let mut current_time = 0i64;
for data_event in input.clone() {
let datapoint = DataPoint {
previous_timestamp: current_time,
current_timestamp: current_time + data_event.time_gap,
slot_gap: data_event.slot_gap,
price: data_event.price,
};

tracker1.add_datapoint(&datapoint).unwrap();
tracker2.add_datapoint(&datapoint).unwrap();
tracker3.add_datapoint(&datapoint).unwrap();
tracker4.add_datapoint(&datapoint).unwrap();
tracker5.add_datapoint(&datapoint).unwrap();
data.push(datapoint);
current_time += data_event.time_gap;

tracker1.check_current_epoch_fields(&data, current_time);
tracker2.check_current_epoch_fields(&data, current_time);
tracker3.check_current_epoch_fields(&data, current_time);
tracker4.check_current_epoch_fields(&data, current_time);
tracker5.check_current_epoch_fields(&data, current_time);
tracker1.check_array_fields(&data, current_time);
tracker2.check_array_fields(&data, current_time);
tracker3.check_array_fields(&data, current_time);
tracker4.check_array_fields(&data, current_time);
tracker5.check_array_fields(&data, current_time);
}

return true;
}


impl<const NUM_ENTRIES: usize> SmaTracker<NUM_ENTRIES> {
pub fn zero() -> Self {
return SmaTracker::<NUM_ENTRIES> {
granularity: 0,
threshold: 0,
current_epoch_denominator: 0,
current_epoch_is_valid: false,
current_epoch_numerator: 0,
running_valid_epoch_counter: [0u64; NUM_ENTRIES],
running_sum_of_price_averages: [0i128; NUM_ENTRIES],
};
}

pub fn check_current_epoch_fields(&self, data: &Vec<DataPoint>, time: i64) {
let curent_epoch = self.time_to_epoch(time).unwrap();

let result = self.compute_epoch_expected_values(data, curent_epoch);
assert_eq!(self.current_epoch_denominator, result.0);
assert_eq!(self.current_epoch_numerator, result.1);
assert_eq!(self.current_epoch_is_valid, result.2);
}

pub fn check_array_fields(&self, data: &Vec<DataPoint>, time: i64) {
let current_epoch = self.time_to_epoch(time).unwrap();
let mut values = vec![];

// Compute all epoch averages
for i in 0..current_epoch {
values.push(self.compute_epoch_expected_values(data, i));
}

// Get running sums
let running_sum_price_iter = values.iter().scan((0, 0), |res, &y| {
res.0 = res.0 + y.1 / i128::from(y.0);
res.1 = res.1 + u64::from(y.2);
Some(*res)
});

// Compare to running_sum_of_price_averages
let mut i = (current_epoch + NUM_ENTRIES - 1).rem_euclid(NUM_ENTRIES);
for x in running_sum_price_iter
.collect::<Vec<(i128, u64)>>()
.iter()
.rev()
.take(NUM_ENTRIES)
{
assert_eq!(self.running_sum_of_price_averages[i], x.0);
assert_eq!(self.running_valid_epoch_counter[i], x.1);
i = (i + NUM_ENTRIES - 1).rem_euclid(NUM_ENTRIES);
}
}

pub fn compute_epoch_expected_values(
&self,
data: &Vec<DataPoint>,
epoch_number: usize,
) -> (u64, i128, bool) {
let left_bound = self
.granularity
.checked_mul(epoch_number.try_into().unwrap())
.unwrap();

let right_bound = self
.granularity
.checked_mul((epoch_number + 1).try_into().unwrap())
.unwrap();


let mut result = data.iter().fold((0, 0, true), |x: (u64, i128, bool), y| {
if !((left_bound > y.current_timestamp) || (right_bound <= y.previous_timestamp))
//Check interval intersection
{
let is_valid = y.slot_gap <= self.threshold;
return (
x.0 + y.slot_gap,
x.1 + i128::from(y.slot_gap) * i128::from(y.price),
x.2 && is_valid,
);
}
return x;
});

if epoch_number == 0 {
result.2 = false;
}
return result;
}
}
Loading