@@ -12,9 +12,12 @@ pub use pyth_sdk::{
12
12
UnixTimestamp ,
13
13
} ;
14
14
#[ cfg( feature = "solana" ) ]
15
- use solitaire:: {
16
- Derive ,
17
- Info ,
15
+ use {
16
+ pyth_sdk_solana:: state:: PriceAccount ,
17
+ solitaire:: {
18
+ Derive ,
19
+ Info ,
20
+ } ,
18
21
} ;
19
22
use {
20
23
serde:: {
@@ -47,12 +50,18 @@ pub const P2W_MAGIC: &[u8] = b"P2WH";
47
50
/// Format version used and understood by this codebase
48
51
pub const P2W_FORMAT_VER_MAJOR : u16 = 3 ;
49
52
50
- /// Starting with v3, format introduces a minor version to mark forward-compatible iterations
51
- pub const P2W_FORMAT_VER_MINOR : u16 = 0 ;
53
+ /// Starting with v3, format introduces a minor version to mark
54
+ /// forward-compatible iterations.
55
+ /// IMPORTANT: Remember to reset this to 0 whenever major version is
56
+ /// bumped.
57
+ /// Changelog:
58
+ /// * v3.1 - last_attested_publish_time field added
59
+ pub const P2W_FORMAT_VER_MINOR : u16 = 1 ;
52
60
53
61
/// Starting with v3, format introduces append-only
54
62
/// forward-compatibility to the header. This is the current number of
55
- /// bytes after the hdr_size field.
63
+ /// bytes after the hdr_size field. After the specified bytes, inner
64
+ /// payload-specific fields begin.
56
65
pub const P2W_FORMAT_HDR_SIZE : u16 = 1 ;
57
66
58
67
pub const PUBKEY_LEN : usize = 32 ;
@@ -80,28 +89,29 @@ pub enum PayloadId {
80
89
#[ serde( rename_all = "camelCase" ) ]
81
90
pub struct PriceAttestation {
82
91
#[ serde( serialize_with = "pubkey_to_hex" ) ]
83
- pub product_id : Identifier ,
92
+ pub product_id : Identifier ,
84
93
#[ serde( serialize_with = "pubkey_to_hex" ) ]
85
- pub price_id : Identifier ,
94
+ pub price_id : Identifier ,
86
95
#[ serde( serialize_with = "use_to_string" ) ]
87
- pub price : i64 ,
96
+ pub price : i64 ,
88
97
#[ serde( serialize_with = "use_to_string" ) ]
89
- pub conf : u64 ,
90
- pub expo : i32 ,
98
+ pub conf : u64 ,
99
+ pub expo : i32 ,
91
100
#[ serde( serialize_with = "use_to_string" ) ]
92
- pub ema_price : i64 ,
101
+ pub ema_price : i64 ,
93
102
#[ serde( serialize_with = "use_to_string" ) ]
94
- pub ema_conf : u64 ,
95
- pub status : PriceStatus ,
96
- pub num_publishers : u32 ,
97
- pub max_num_publishers : u32 ,
98
- pub attestation_time : UnixTimestamp ,
99
- pub publish_time : UnixTimestamp ,
100
- pub prev_publish_time : UnixTimestamp ,
103
+ pub ema_conf : u64 ,
104
+ pub status : PriceStatus ,
105
+ pub num_publishers : u32 ,
106
+ pub max_num_publishers : u32 ,
107
+ pub attestation_time : UnixTimestamp ,
108
+ pub publish_time : UnixTimestamp ,
109
+ pub prev_publish_time : UnixTimestamp ,
101
110
#[ serde( serialize_with = "use_to_string" ) ]
102
- pub prev_price : i64 ,
111
+ pub prev_price : i64 ,
103
112
#[ serde( serialize_with = "use_to_string" ) ]
104
- pub prev_conf : u64 ,
113
+ pub prev_conf : u64 ,
114
+ pub last_attested_publish_time : UnixTimestamp ,
105
115
}
106
116
107
117
/// Helper allowing ToString implementers to be serialized as strings accordingly
@@ -146,6 +156,10 @@ impl BatchPriceAttestation {
146
156
// payload_id
147
157
buf. push ( PayloadId :: PriceBatchAttestation as u8 ) ;
148
158
159
+ // Header is over. NOTE: If you need to append to the header,
160
+ // make sure that the number of bytes after hdr_size is
161
+ // reflected in the P2W_FORMAT_HDR_SIZE constant.
162
+
149
163
// n_attestations
150
164
buf. extend_from_slice ( & ( self . price_attestations . len ( ) as u16 ) . to_be_bytes ( ) [ ..] ) ;
151
165
@@ -279,11 +293,25 @@ impl PriceAttestation {
279
293
pub fn from_pyth_price_bytes (
280
294
price_id : Identifier ,
281
295
attestation_time : UnixTimestamp ,
296
+ last_attested_publish_time : UnixTimestamp ,
282
297
value : & [ u8 ] ,
283
298
) -> Result < Self , ErrBox > {
284
- let price = pyth_sdk_solana:: state:: load_price_account ( value) ?;
285
-
286
- Ok ( PriceAttestation {
299
+ let price_struct = pyth_sdk_solana:: state:: load_price_account ( value) ?;
300
+ Ok ( Self :: from_pyth_price_struct (
301
+ price_id,
302
+ attestation_time,
303
+ last_attested_publish_time,
304
+ price_struct,
305
+ ) )
306
+ }
307
+ #[ cfg( feature = "solana" ) ]
308
+ pub fn from_pyth_price_struct (
309
+ price_id : Identifier ,
310
+ attestation_time : UnixTimestamp ,
311
+ last_attested_publish_time : UnixTimestamp ,
312
+ price : & PriceAccount ,
313
+ ) -> Self {
314
+ PriceAttestation {
287
315
product_id : Identifier :: new ( price. prod . val ) ,
288
316
price_id,
289
317
price : price. agg . price ,
@@ -299,7 +327,8 @@ impl PriceAttestation {
299
327
prev_publish_time : price. prev_timestamp ,
300
328
prev_price : price. prev_price ,
301
329
prev_conf : price. prev_conf ,
302
- } )
330
+ last_attested_publish_time,
331
+ }
303
332
}
304
333
305
334
/// Serialize this attestation according to the Pyth-over-wormhole serialization format
@@ -322,6 +351,7 @@ impl PriceAttestation {
322
351
prev_publish_time,
323
352
prev_price,
324
353
prev_conf,
354
+ last_attested_publish_time,
325
355
} = self ;
326
356
327
357
let mut buf = Vec :: new ( ) ;
@@ -371,6 +401,9 @@ impl PriceAttestation {
371
401
// prev_conf
372
402
buf. extend_from_slice ( & prev_conf. to_be_bytes ( ) [ ..] ) ;
373
403
404
+ // last_attested_publish_time
405
+ buf. extend_from_slice ( & last_attested_publish_time. to_be_bytes ( ) [ ..] ) ;
406
+
374
407
buf
375
408
}
376
409
pub fn deserialize ( mut bytes : impl Read ) -> Result < Self , ErrBox > {
@@ -444,6 +477,11 @@ impl PriceAttestation {
444
477
bytes. read_exact ( prev_conf_vec. as_mut_slice ( ) ) ?;
445
478
let prev_conf = u64:: from_be_bytes ( prev_conf_vec. as_slice ( ) . try_into ( ) ?) ;
446
479
480
+ let mut last_attested_publish_time_vec = vec ! [ 0u8 ; mem:: size_of:: <UnixTimestamp >( ) ] ;
481
+ bytes. read_exact ( last_attested_publish_time_vec. as_mut_slice ( ) ) ?;
482
+ let last_attested_publish_time =
483
+ UnixTimestamp :: from_be_bytes ( last_attested_publish_time_vec. as_slice ( ) . try_into ( ) ?) ;
484
+
447
485
Ok ( Self {
448
486
product_id,
449
487
price_id,
@@ -460,6 +498,7 @@ impl PriceAttestation {
460
498
prev_publish_time,
461
499
prev_price,
462
500
prev_conf,
501
+ last_attested_publish_time,
463
502
} )
464
503
}
465
504
}
@@ -478,21 +517,22 @@ mod tests {
478
517
let product_id_bytes = prod. unwrap_or ( [ 21u8 ; 32 ] ) ;
479
518
let price_id_bytes = price. unwrap_or ( [ 222u8 ; 32 ] ) ;
480
519
PriceAttestation {
481
- product_id : Identifier :: new ( product_id_bytes) ,
482
- price_id : Identifier :: new ( price_id_bytes) ,
483
- price : 0x2bad2feed7 ,
484
- conf : 101 ,
485
- ema_price : -42 ,
486
- ema_conf : 42 ,
487
- expo : -3 ,
488
- status : PriceStatus :: Trading ,
489
- num_publishers : 123212u32 ,
490
- max_num_publishers : 321232u32 ,
491
- attestation_time : ( 0xdeadbeeffadedeedu64 ) as i64 ,
492
- publish_time : 0xdadebeefi64 ,
493
- prev_publish_time : 0xdeadbabei64 ,
494
- prev_price : 0xdeadfacebeefi64 ,
495
- prev_conf : 0xbadbadbeefu64 , // I could do this all day -SD
520
+ product_id : Identifier :: new ( product_id_bytes) ,
521
+ price_id : Identifier :: new ( price_id_bytes) ,
522
+ price : 0x2bad2feed7 ,
523
+ conf : 101 ,
524
+ ema_price : -42 ,
525
+ ema_conf : 42 ,
526
+ expo : -3 ,
527
+ status : PriceStatus :: Trading ,
528
+ num_publishers : 123212u32 ,
529
+ max_num_publishers : 321232u32 ,
530
+ attestation_time : ( 0xdeadbeeffadedeedu64 ) as i64 ,
531
+ publish_time : 0xdadebeefi64 ,
532
+ prev_publish_time : 0xdeadbabei64 ,
533
+ prev_price : 0xdeadfacebeefi64 ,
534
+ prev_conf : 0xbadbadbeefu64 , // I could do this all day -SD
535
+ last_attested_publish_time : ( 0xdeadbeeffadedeafu64 ) as i64 ,
496
536
}
497
537
}
498
538
0 commit comments