Skip to content

Commit 77383e7

Browse files
authored
Allow negative prices in aggregation (#344)
* make it build * allow negative prices * clean up comments * doc * fixme * cleanup * fix aggregation tests * fix ema stuff too * delet
1 parent 71d2394 commit 77383e7

File tree

8 files changed

+110
-21
lines changed

8 files changed

+110
-21
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,3 @@ root@pyth-dev# usermod -u 1002 -g 1004 -s /bin/bash pyth
145145
```
146146

147147
Finally, in docker extension inside VS Code click right and choose "Attach VS Code". If you're using a remote host in VS Code make sure to let this connection be open.
148-

program/c/src/oracle/upd_aggregate.h

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,12 @@ static void upd_ema(
102102
// compute numer/denom and new value from decay factor
103103
pd_load( numer, ptr->numer_ );
104104
pd_load( denom, ptr->denom_ );
105-
if ( numer->v_ < 0 || denom->v_ < 0 ) {
106-
// temporary reset twap on negative value
107-
pd_set( numer, val );
108-
pd_set( denom, one );
109-
}
110-
else {
111-
pd_mul( numer, numer, decay );
112-
pd_mul( wval, val, cwgt );
113-
pd_add( numer, numer, wval, qs->fact_ );
114-
pd_mul( denom, denom, decay );
115-
pd_add( denom, denom, cwgt, qs->fact_ );
116-
pd_div( val, numer, denom );
117-
}
105+
pd_mul( numer, numer, decay );
106+
pd_mul( wval, val, cwgt );
107+
pd_add( numer, numer, wval, qs->fact_ );
108+
pd_mul( denom, denom, decay );
109+
pd_add( denom, denom, cwgt, qs->fact_ );
110+
pd_div( val, numer, denom );
118111
}
119112

120113
// adjust and store results
@@ -180,12 +173,14 @@ static inline bool upd_aggregate( pc_price_t *ptr, uint64_t slot, int64_t timest
180173
int64_t price = iptr->agg_.price_;
181174
int64_t conf = ( int64_t )( iptr->agg_.conf_ );
182175
if ( iptr->agg_.status_ == PC_STATUS_TRADING &&
183-
(int64_t)0 < conf && conf < price && conf <= (INT64_MAX-price) && // No overflow for INT64_MAX-price as price>0
176+
// No overflow for INT64_MIN+conf or INT64_MAX-conf as 0 < conf < INT64_MAX
177+
// These checks ensure that price - conf and price + conf do not overflow.
178+
(int64_t)0 < conf && (INT64_MIN + conf) <= price && price <= (INT64_MAX-conf) &&
184179
slot_diff >= 0 && slot_diff <= PC_MAX_SEND_LATENCY ) {
185180
numv += 1;
186-
prcs[ nprcs++ ] = price - conf; // No overflow as 0 < conf < price
181+
prcs[ nprcs++ ] = price - conf;
187182
prcs[ nprcs++ ] = price;
188-
prcs[ nprcs++ ] = price + conf; // No overflow as 0 < conf <= INT64_MAX-price
183+
prcs[ nprcs++ ] = price + conf;
189184
}
190185
}
191186

program/rust/src/tests/test_ema.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
extern crate test_generator;
2+
23
use {
34
crate::{
45
accounts::PriceAccount,
@@ -20,6 +21,41 @@ use {
2021

2122
#[test_resources("program/rust/test_data/ema/*.csv")]
2223
fn test_ema(input_path_raw: &str) {
24+
let (inputs, expected_outputs) = read_test_data(input_path_raw);
25+
run_ema_test(&inputs, &expected_outputs);
26+
}
27+
28+
// This test is identical to the one above, except that it negates all of the prices.
29+
#[test_resources("program/rust/test_data/ema/*.csv")]
30+
fn test_ema_negated(input_path_raw: &str) {
31+
let (inputs, expected_outputs) = read_test_data(input_path_raw);
32+
33+
let modified_inputs: Vec<InputRecord> = inputs
34+
.iter()
35+
.map(|x| InputRecord {
36+
price: -x.price,
37+
conf: x.conf,
38+
expo: x.expo,
39+
nslots: x.nslots,
40+
})
41+
.collect();
42+
43+
let modified_outputs: Vec<OutputRecord> = expected_outputs
44+
.iter()
45+
.map(|x| OutputRecord {
46+
price: -x.price,
47+
conf: x.conf,
48+
expo: x.expo,
49+
nslots: x.nslots,
50+
twap: -x.twap,
51+
twac: x.twac,
52+
})
53+
.collect();
54+
55+
run_ema_test(&modified_inputs, &modified_outputs);
56+
}
57+
58+
fn read_test_data(input_path_raw: &str) -> (Vec<InputRecord>, Vec<OutputRecord>) {
2359
// For some reason these tests have a different working directory than the macro.
2460
let input_path = input_path_raw.replace("program/rust/", "");
2561
let result_path = input_path.replace(".csv", ".result");
@@ -48,6 +84,10 @@ fn test_ema(input_path_raw: &str) {
4884
"Mismatched CSV file lengths"
4985
);
5086

87+
(inputs, expected_outputs)
88+
}
89+
90+
fn run_ema_test(inputs: &[InputRecord], expected_outputs: &[OutputRecord]) {
5191
let current_slot = 1000;
5292
let current_timestamp = 1234;
5393
let mut price_account: PriceAccount = PriceAccount::zeroed();
@@ -70,6 +110,7 @@ fn test_ema(input_path_raw: &str) {
70110
}
71111
}
72112

113+
73114
// TODO: put these functions somewhere more accessible
74115
pub fn upd_aggregate(
75116
price_account: &mut PriceAccount,

program/rust/src/tests/test_upd_price.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,60 @@ fn test_upd_price() {
277277
assert_eq!(price_data.agg_.price_, 81);
278278
assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN);
279279
}
280+
281+
// Negative prices are accepted
282+
populate_instruction(&mut instruction_data, -100, 1, 7);
283+
update_clock_slot(&mut clock_account, 8);
284+
285+
assert!(process_instruction(
286+
&program_id,
287+
&[
288+
funding_account.clone(),
289+
price_account.clone(),
290+
clock_account.clone()
291+
],
292+
&instruction_data
293+
)
294+
.is_ok());
295+
296+
{
297+
let price_data = load_checked::<PriceAccount>(&price_account, PC_VERSION).unwrap();
298+
assert_eq!(price_data.comp_[0].latest_.price_, -100);
299+
assert_eq!(price_data.comp_[0].latest_.conf_, 1);
300+
assert_eq!(price_data.comp_[0].latest_.pub_slot_, 7);
301+
assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING);
302+
assert_eq!(price_data.valid_slot_, 7);
303+
assert_eq!(price_data.agg_.pub_slot_, 8);
304+
assert_eq!(price_data.agg_.price_, 81);
305+
assert_eq!(price_data.agg_.status_, PC_STATUS_UNKNOWN);
306+
}
307+
308+
// Crank again for aggregate
309+
populate_instruction(&mut instruction_data, -100, 1, 8);
310+
update_clock_slot(&mut clock_account, 9);
311+
312+
assert!(process_instruction(
313+
&program_id,
314+
&[
315+
funding_account.clone(),
316+
price_account.clone(),
317+
clock_account.clone()
318+
],
319+
&instruction_data
320+
)
321+
.is_ok());
322+
323+
{
324+
let price_data = load_checked::<PriceAccount>(&price_account, PC_VERSION).unwrap();
325+
assert_eq!(price_data.comp_[0].latest_.price_, -100);
326+
assert_eq!(price_data.comp_[0].latest_.conf_, 1);
327+
assert_eq!(price_data.comp_[0].latest_.pub_slot_, 8);
328+
assert_eq!(price_data.comp_[0].latest_.status_, PC_STATUS_TRADING);
329+
assert_eq!(price_data.valid_slot_, 8);
330+
assert_eq!(price_data.agg_.pub_slot_, 9);
331+
assert_eq!(price_data.agg_.price_, -100);
332+
assert_eq!(price_data.agg_.status_, PC_STATUS_TRADING);
333+
}
280334
}
281335

282336
// Create an upd_price instruction with the provided parameters
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"exponent":-3,"price":0,"conf":0,"status":"unknown"}
1+
{"exponent":-3,"price":-10000,"conf":1000,"status":"trading"}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"exponent":-3,"price":0,"conf":0,"status":"unknown"}
1+
{"exponent":-3,"price":0,"conf":1000,"status":"trading"}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"exponent":-8,"price":4329187000000,"conf":3187000000,"status":"trading"}
1+
{"exponent":-8,"price":4328634500000,"conf":2914500000,"status":"trading"}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"exponent":-8,"price":4329402176469,"conf":2206823531,"status":"trading"}
1+
{"exponent":-8,"price":4329187000000,"conf":3187000000,"status":"trading"}

0 commit comments

Comments
 (0)